@@ -197,6 +314,337 @@ export function LogFilters({
+
+
+
+
+
+
+
+
+
+
+ {selectedStatusCodes.length > 0 && (
+
+ {selectedStatusCodes.map((code) => (
+
+ toggleSelection(
+ selectedStatusCodes,
+ code,
+ onStatusCodesChange
+ )
+ }
+ >
+
+ {code}
+
+ ))}
+
+ )}
+ {statusSearch &&
+ !STATUS_CODE_OPTIONS.includes(statusSearch) &&
+ !selectedStatusCodes.includes(statusSearch) && (
+
+ {
+ if (/^\d+$/.test(statusSearch)) {
+ toggleSelection(
+ selectedStatusCodes,
+ statusSearch,
+ onStatusCodesChange
+ );
+ setStatusSearch('');
+ }
+ }}
+ >
+
+ Add "{statusSearch}"
+
+
+ )}
+ No results found.
+
+ handleQuickStatusCode('4xx')}
+ >
+
+ c.startsWith('4')
+ ).every((c) => selectedStatusCodes.includes(c))}
+ className='mr-2'
+ />
+ 4xx Errors
+
+ handleQuickStatusCode('5xx')}
+ >
+
+ c.startsWith('5')
+ ).every((c) => selectedStatusCodes.includes(c))}
+ className='mr-2'
+ />
+ 5xx Errors
+
+
+
+ {STATUS_CODE_OPTIONS.filter(
+ (code) => !selectedStatusCodes.includes(code)
+ ).map((code) => (
+
+ toggleSelection(
+ selectedStatusCodes,
+ code,
+ onStatusCodesChange
+ )
+ }
+ >
+
+ {code}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {selectedMethods.length > 0 && (
+
+ {selectedMethods.map((method) => (
+
+ toggleSelection(
+ selectedMethods,
+ method,
+ onMethodsChange
+ )
+ }
+ >
+
+ {method}
+
+ ))}
+
+ )}
+ {methodSearch &&
+ !METHOD_OPTIONS.includes(methodSearch.toUpperCase()) &&
+ !selectedMethods.includes(methodSearch.toUpperCase()) && (
+
+ {
+ toggleSelection(
+ selectedMethods,
+ methodSearch.toUpperCase(),
+ onMethodsChange
+ );
+ setMethodSearch('');
+ }}
+ >
+
+ Add "{methodSearch.toUpperCase()}"
+
+
+ )}
+ No results found.
+
+ {METHOD_OPTIONS.filter(
+ (method) => !selectedMethods.includes(method)
+ ).map((method) => (
+
+ toggleSelection(
+ selectedMethods,
+ method,
+ onMethodsChange
+ )
+ }
+ >
+
+ {method}
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {selectedEndpoints.length > 0 && (
+
+ {selectedEndpoints.map((endpoint) => (
+
+ toggleSelection(
+ selectedEndpoints,
+ endpoint,
+ onEndpointsChange
+ )
+ }
+ >
+
+ {endpoint}
+
+ ))}
+
+ )}
+ {endpointSearch &&
+ !ENDPOINT_OPTIONS.includes(endpointSearch) &&
+ !selectedEndpoints.includes(endpointSearch) && (
+
+ {
+ toggleSelection(
+ selectedEndpoints,
+ endpointSearch,
+ onEndpointsChange
+ );
+ setEndpointSearch('');
+ }}
+ >
+
+ Add "{endpointSearch}"
+
+
+ )}
+ No results found.
+
+ {ENDPOINT_OPTIONS.filter(
+ (endpoint) => !selectedEndpoints.includes(endpoint)
+ ).map((endpoint) => (
+
+ toggleSelection(
+ selectedEndpoints,
+ endpoint,
+ onEndpointsChange
+ )
+ }
+ >
+
+ {endpoint}
+
+ ))}
+
+
+
+
+
+
+
('all');
const [selectedLevel, setSelectedLevel] = useState('all');
const [requestId, setRequestId] = useState('');
const [searchText, setSearchText] = useState('');
+ const [selectedStatusCodes, setSelectedStatusCodes] = useState([]);
+ const [selectedMethods, setSelectedMethods] = useState([]);
+ const [selectedEndpoints, setSelectedEndpoints] = useState([]);
const [limit, setLimit] = useState(100);
const [selectedLog, setSelectedLog] = useState(null);
const [isDialogOpen, setIsDialogOpen] = useState(false);
+ // Load filters from localStorage on mount
+ useEffect(() => {
+ const saved = localStorage.getItem(STORAGE_KEY);
+ if (saved) {
+ try {
+ const parsed = JSON.parse(saved);
+ if (parsed.selectedDate) setSelectedDate(parsed.selectedDate);
+ if (parsed.selectedLevel) setSelectedLevel(parsed.selectedLevel);
+ if (parsed.requestId) setRequestId(parsed.requestId);
+ if (parsed.searchText) setSearchText(parsed.searchText);
+ if (parsed.selectedStatusCodes)
+ setSelectedStatusCodes(parsed.selectedStatusCodes);
+ if (parsed.selectedMethods) setSelectedMethods(parsed.selectedMethods);
+ if (parsed.selectedEndpoints)
+ setSelectedEndpoints(parsed.selectedEndpoints);
+ if (parsed.limit) setLimit(parsed.limit);
+ } catch (e) {
+ console.error('Failed to load filters from localStorage', e);
+ }
+ }
+ }, []);
+
+ // Save filters to localStorage whenever they change
+ useEffect(() => {
+ const filters = {
+ selectedDate,
+ selectedLevel,
+ requestId,
+ searchText,
+ selectedStatusCodes,
+ selectedMethods,
+ selectedEndpoints,
+ limit,
+ };
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(filters));
+ }, [
+ selectedDate,
+ selectedLevel,
+ requestId,
+ searchText,
+ selectedStatusCodes,
+ selectedMethods,
+ selectedEndpoints,
+ limit,
+ ]);
+
const {
data: logsData,
refetch: refetchLogs,
@@ -42,6 +93,9 @@ export default function LogsPage() {
selectedLevel,
requestId,
searchText,
+ selectedStatusCodes,
+ selectedMethods,
+ selectedEndpoints,
limit,
],
queryFn: () =>
@@ -50,6 +104,16 @@ export default function LogsPage() {
level: selectedLevel === 'all' ? undefined : selectedLevel,
request_id: requestId || undefined,
search: searchText || undefined,
+ status_codes:
+ selectedStatusCodes.length > 0
+ ? selectedStatusCodes.join(',')
+ : undefined,
+ methods:
+ selectedMethods.length > 0 ? selectedMethods.join(',') : undefined,
+ endpoints:
+ selectedEndpoints.length > 0
+ ? selectedEndpoints.join(',')
+ : undefined,
limit: limit,
}),
refetchInterval: 30000,
@@ -60,6 +124,9 @@ export default function LogsPage() {
setSelectedLevel('all');
setRequestId('');
setSearchText('');
+ setSelectedStatusCodes([]);
+ setSelectedMethods([]);
+ setSelectedEndpoints([]);
setLimit(100);
};
@@ -100,11 +167,17 @@ export default function LogsPage() {
selectedLevel={selectedLevel}
requestId={requestId}
searchText={searchText}
+ selectedStatusCodes={selectedStatusCodes}
+ selectedMethods={selectedMethods}
+ selectedEndpoints={selectedEndpoints}
limit={limit}
onDateChange={setSelectedDate}
onLevelChange={setSelectedLevel}
onRequestIdChange={setRequestId}
onSearchTextChange={setSearchText}
+ onStatusCodesChange={setSelectedStatusCodes}
+ onMethodsChange={setSelectedMethods}
+ onEndpointsChange={setSelectedEndpoints}
onLimitChange={setLimit}
onClearFilters={handleClearFilters}
/>
@@ -122,13 +195,22 @@ export default function LogsPage() {
{(selectedDate !== 'all' ||
selectedLevel !== 'all' ||
requestId ||
- searchText) && (
+ searchText ||
+ selectedStatusCodes.length > 0 ||
+ selectedMethods.length > 0 ||
+ selectedEndpoints.length > 0) && (
Showing logs
{selectedDate !== 'all' && ` for ${selectedDate}`}
{selectedLevel !== 'all' && ` with level ${selectedLevel}`}
{requestId && ` with request ID ${requestId}`}
{searchText && ` matching "${searchText}"`}
+ {selectedStatusCodes.length > 0 &&
+ ` with status ${selectedStatusCodes.join(', ')}`}
+ {selectedMethods.length > 0 &&
+ ` with method ${selectedMethods.join(', ')}`}
+ {selectedEndpoints.length > 0 &&
+ ` with endpoint ${selectedEndpoints.join(', ')}`}
)}
diff --git a/ui/app/logs/types.ts b/ui/app/logs/types.ts
index 8c905827..a9aa8f33 100644
--- a/ui/app/logs/types.ts
+++ b/ui/app/logs/types.ts
@@ -17,6 +17,9 @@ export interface LogsResponse {
level: string | null;
request_id: string | null;
search: string | null;
+ status_codes: string | null;
+ methods: string | null;
+ endpoints: string | null;
limit: number;
}