- {getFormattedDate(timestamp)}
-
- {level}
-
-
- {module}
- {' '}
- {getFormattedMessage(message)}
+
+
+ {isLoading ? (
+
+ ) : (
+ <>
+ {error ? (
+
+
+
+ Could not load logs
+
+ {error}
+ {onRetry ? (
+
+ ) : null}
+
+
-
-
-
- {copied ? (
-
-
-
- ) : (
-
- )}
-
-
- );
- })}
-
+ ) : null}
+ {!error && logs.length === 0 ? (
+
+ ) : null}
+ {logs.length > 0 ? (
+
+ {logs.map((log, index) => {
+ const itemId = `item-${log.timestamp}-${index}`;
+
+ return (
+
+ );
+ })}
+
+ ) : null}
+ >
+ )}
+
+ {!followLatest && logs.length > 0 && !isLoading ? (
+
+ ) : null}
+
);
}
diff --git a/src/components/logs-viewer/LogsDrawer.tsx b/src/components/logs-viewer/LogsDrawer.tsx
index 96ed542d5..f119f1b8b 100644
--- a/src/components/logs-viewer/LogsDrawer.tsx
+++ b/src/components/logs-viewer/LogsDrawer.tsx
@@ -1,7 +1,9 @@
'use client';
+
import { Button } from '@/components/ui/button';
import {
Drawer,
+ DrawerClose,
DrawerContent,
DrawerTitle,
DrawerTrigger,
@@ -10,9 +12,9 @@ import { Logs, X } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { usePathname } from 'next/navigation';
import { cn } from '@/lib/utils';
-import { LogsAccordionList } from './LogsAccordionList';
-import LogsFiltersPanel from './LogsFiltersPanel';
-import { LogsData } from '@/lib/models/logs-viewer';
+import { LogsWorkspace } from './LogsWorkspace';
+import { EmptyState } from '@/components/ui/empty-state';
+import { LogsData, LogsQueryParams } from '@/lib/models/logs-viewer';
import {
getLogsLevels,
getLogsQueryRange,
@@ -21,22 +23,21 @@ import {
import { getLokiAvailability } from '@/lib/observability/lokiAvailability.actions';
import type { LokiAvailability } from '@/lib/observability/types';
import { knownModuleNames } from '@/lib/models/logs-viewer/constants';
-import { getLokiModuleFilterForPath } from '@/lib/utils/module-utils';
+import {
+ getLokiModuleFilterForPath,
+ getModuleDisplayName,
+} from '@/lib/utils/module-utils';
const snapPoints = [0.5, 0.75, 1];
-type LogsDrawerProps = {
- isSidebarOpen?: boolean;
-};
-
-export function LogsDrawer({ isSidebarOpen = true }: LogsDrawerProps) {
+export function LogsDrawer() {
const [lokiAvailability, setLokiAvailability] =
useState
(null);
const [snap, setSnap] = useState(snapPoints[0]);
const [levels, setLevels] = useState([]);
const [modules, setModules] = useState([]);
const [logs, setLogs] = useState([]);
- const [drawerHeight, setDrawerHeight] = useState(0);
+ const [isLoading, setIsLoading] = useState(true);
const pathname = usePathname();
const drawerModuleLabel = useMemo(
() =>
@@ -59,19 +60,17 @@ export function LogsDrawer({ isSidebarOpen = true }: LogsDrawerProps) {
() => drawerModuleLabel === 'core',
[drawerModuleLabel]
);
+ const moduleDisplayName =
+ drawerModuleLabel === 'core'
+ ? 'Core'
+ : getModuleDisplayName(drawerModuleLabel);
useEffect(() => {
getLokiAvailability().then(setLokiAvailability);
}, [drawerModuleLabel]);
const refreshDrawerLogs = useCallback(
- async (data: {
- modules: string[] | string;
- levels?: string[];
- startDate?: number;
- endDate?: number;
- limit?: string;
- }) => {
+ async (data: LogsQueryParams) => {
return await getLogsQueryRange({
...data,
modules: lokiModuleFilter,
@@ -80,6 +79,7 @@ export function LogsDrawer({ isSidebarOpen = true }: LogsDrawerProps) {
},
[lokiModuleFilter]
);
+
const lokiReady = lokiAvailability?.state === 'ready';
const showLogsUi =
!isLogsViewerPage &&
@@ -87,16 +87,20 @@ export function LogsDrawer({ isSidebarOpen = true }: LogsDrawerProps) {
lokiAvailability.state !== 'not_configured';
useEffect(() => {
- if (!lokiReady) return;
+ if (!lokiReady) {
+ setIsLoading(false);
+ return;
+ }
+ setIsLoading(true);
getLogsLevels()
.then(res => {
- 'use client';
setLevels(res);
})
- .catch();
+ .catch(() => {
+ setLevels([]);
+ });
getModules()
.then(res => {
- 'use client';
let validModules = knownModuleNames;
if (res.length > 0) {
validModules = knownModuleNames.concat(
@@ -105,28 +109,15 @@ export function LogsDrawer({ isSidebarOpen = true }: LogsDrawerProps) {
}
setModules(validModules);
})
- .catch();
- refreshDrawerLogs({ modules: lokiModuleFilter })
- .then(res => {
- 'use client';
- setLogs(res);
- })
- .catch();
- }, [lokiReady, lokiModuleFilter, pathname, refreshDrawerLogs]);
-
- useEffect(() => {
- const height = calculateDrawerHeight() - 124; // subtract height for drawer header & drawer vertical padding
- setDrawerHeight(height);
- }, [snap]);
-
- const calculateDrawerHeight = () => {
- const windowHeight = window.innerHeight;
- return (snap as number) ? windowHeight * (snap as number) : windowHeight;
- };
+ .catch(() => {
+ setModules([...knownModuleNames]);
+ });
+ }, [lokiReady, lokiModuleFilter, pathname]);
return showLogsUi ? (
- List of logs with filters
-
-
-
- {lokiReady ? (
- <>
-
+
+
+
+ Logs
+
+
+ {moduleDisplayName}
+
+
+
+
+
+
+ {lokiReady ? (
+ {
+ setLogs(value);
+ setIsLoading(false);
+ }}
modules={modules}
- drawerModule={drawerModuleLabel}
refreshLogs={refreshDrawerLogs}
+ drawerModule={drawerModuleLabel}
+ type="drawer"
+ isLoading={isLoading}
/>
-
-
-
- >
- ) : (
-
- Cannot reach Loki at the configured URL. Check that Loki is running
- and that LOKI_URL is correct.
-
- )}
+ ) : (
+
+ )}
+
) : null;
diff --git a/src/components/logs-viewer/LogsFiltersOptions.tsx b/src/components/logs-viewer/LogsFiltersOptions.tsx
index ecd0d7778..47c6d88bb 100644
--- a/src/components/logs-viewer/LogsFiltersOptions.tsx
+++ b/src/components/logs-viewer/LogsFiltersOptions.tsx
@@ -1,3 +1,5 @@
+'use client';
+
import { generateMultiSelectOptions } from '@/lib/models/logs-viewer/utils';
import MultiSelectField from '@/components/ui/form-inputs/MultiSelectField';
import SelectField from '@/components/ui/form-inputs/SelectField';
@@ -6,30 +8,15 @@ import { limitOptions, timeOptions } from '@/lib/models/logs-viewer/constants';
import { cn } from '@/lib/utils';
import { DatePickerField } from '@/components/ui/form-inputs/DatePickerField';
import { Dispatch, SetStateAction, useEffect } from 'react';
-import { usePathname, useRouter, useSearchParams } from 'next/navigation';
+import { usePathname, useRouter } from 'next/navigation';
+import { LogsFiltersState } from '@/lib/models/logs-viewer';
-interface LogsFiltersOptionsProps extends React.FormHTMLAttributes