Skip to content

Commit f9087b8

Browse files
authored
Empty state (#26)
* add empty state * table loading state * add bars * add bars * add table loading state * bar sizes * fix selector * fix pipe
1 parent 45b0fb0 commit f9087b8

File tree

6 files changed

+149
-128
lines changed

6 files changed

+149
-128
lines changed

dashboard/log-analyzer/src/components/charts/TimeSeriesChart.tsx

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,13 @@ export function TimeSeriesChart(params: {
4444
}
4545
})
4646

47-
if (loading) return <div>Loading...</div>
47+
if (loading) {
48+
return (
49+
<div className="">
50+
<div className="h-[140px] bg-[#D9D9D9] rounded-[4px] animation-pulse" />
51+
</div>
52+
);
53+
}
4854
if (error) return <div>Error: {error}</div>
4955

5056
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -130,7 +136,8 @@ export function TimeSeriesChart(params: {
130136
name: 'Total Requests',
131137
type: 'bar',
132138
stack: 'Total',
133-
maxBarWidth: 8,
139+
barMinWidth: 1,
140+
barMaxWidth: 8,
134141
barGap: '20%',
135142
large: true,
136143
itemStyle: {

dashboard/log-analyzer/src/components/filters/DateRangeSelector.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export function DateRangeSelector() {
2121
let startDate: Date;
2222

2323
switch (value) {
24+
case '5m':
25+
startDate = new Date(now.getTime() - 30 * 60 * 1000);
26+
break;
2427
case '30m':
2528
startDate = new Date(now.getTime() - 30 * 60 * 1000);
2629
break;
@@ -42,9 +45,6 @@ export function DateRangeSelector() {
4245
case '6m':
4346
startDate = new Date(now.getTime() - 6 * 30 * 24 * 60 * 60 * 1000);
4447
break;
45-
case '1y':
46-
startDate = new Date(now.getTime() - 365 * 24 * 60 * 60 * 1000);
47-
break;
4848
default:
4949
startDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
5050
}
@@ -62,14 +62,14 @@ export function DateRangeSelector() {
6262
<SelectValue placeholder="Time Range" />
6363
</SelectTrigger>
6464
<SelectContent>
65+
<SelectItem value="5m" className="hover:bg-[var(--background-hover)]">Last 5 minutes</SelectItem>
6566
<SelectItem value="30m" className="hover:bg-[var(--background-hover)]">Last 30 minutes</SelectItem>
6667
<SelectItem value="1h" className="hover:bg-[var(--background-hover)]">Last 1 hour</SelectItem>
6768
<SelectItem value="24h" className="hover:bg-[var(--background-hover)]">Last 24 hours</SelectItem>
6869
<SelectItem value="7d" className="hover:bg-[var(--background-hover)]">Last 7 days</SelectItem>
6970
<SelectItem value="30d" className="hover:bg-[var(--background-hover)]">Last 30 days</SelectItem>
7071
<SelectItem value="3m" className="hover:bg-[var(--background-hover)]">Last 3 months</SelectItem>
7172
<SelectItem value="6m" className="hover:bg-[var(--background-hover)]">Last 6 months</SelectItem>
72-
<SelectItem value="1y" className="hover:bg-[var(--background-hover)]">Last 1 year</SelectItem>
7373
</SelectContent>
7474
</Select>
7575
);

dashboard/log-analyzer/src/components/icons/index.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,10 @@ export const ExpandIcon = () => (
1212
<path fillRule="evenodd" clipRule="evenodd" d="M9.45544 13.6667C9.45544 13.2994 9.75317 13.0017 10.1204 13.0017L11.8347 13.0017C12.113 13.0017 12.3799 12.8911 12.5767 12.6944C12.7735 12.4976 12.884 12.2307 12.884 11.9524L12.884 3.38098C12.884 3.10269 12.7735 2.83581 12.5767 2.63903C12.3799 2.44225 12.113 2.3317 11.8347 2.3317L10.1204 2.3317C9.75318 2.3317 9.45545 2.03397 9.45545 1.6667C9.45545 1.29943 9.75318 1.0017 10.1204 1.0017L11.8347 1.0017C12.4658 1.0017 13.0709 1.25237 13.5171 1.69857C13.9633 2.14478 14.214 2.74996 14.214 3.38098L14.214 11.9524C14.214 12.5834 13.9633 13.1886 13.5171 13.6348C13.0709 14.081 12.4658 14.3317 11.8347 14.3317L10.1204 14.3317C9.75317 14.3317 9.45544 14.034 9.45544 13.6667Z" fill="currentColor"/>
1313
</g>
1414
</svg>
15-
);
15+
);
16+
17+
export const FileIcon = () => (
18+
<svg width="16" height="21" viewBox="0 0 16 21" fill="none" xmlns="http://www.w3.org/2000/svg">
19+
<path d="M8.92 14.745C8.89811 14.6813 8.86785 14.6207 8.83 14.565C8.79308 14.5126 8.75301 14.4625 8.71 14.415C8.56938 14.2762 8.3908 14.1822 8.19681 14.1449C8.00282 14.1075 7.80211 14.1284 7.62 14.205C7.49725 14.2526 7.3851 14.324 7.29 14.415C7.24699 14.4625 7.20692 14.5126 7.17 14.565C7.13215 14.6207 7.10189 14.6813 7.08 14.745C7.05117 14.8017 7.03095 14.8624 7.02 14.925C7.01555 14.9916 7.01555 15.0584 7.02 15.125C7.01662 15.2562 7.04402 15.3863 7.1 15.505C7.15064 15.6262 7.22167 15.7378 7.31 15.835C7.49379 16.0173 7.74113 16.1213 8 16.125C8.13118 16.1284 8.26132 16.101 8.38 16.045C8.5041 16.0001 8.6168 15.9284 8.71011 15.8351C8.80343 15.7418 8.87509 15.6291 8.92 15.505C8.97598 15.3863 9.00338 15.2562 9 15.125C9.00445 15.0584 9.00445 14.9916 9 14.925C8.98278 14.8612 8.9558 14.8005 8.92 14.745ZM16 7.065C15.9896 6.97313 15.9695 6.88263 15.94 6.795V6.705C15.8919 6.60218 15.8278 6.50767 15.75 6.425L9.75 0.425C9.66734 0.347216 9.57282 0.283081 9.47 0.235H9.38C9.27841 0.176741 9.16622 0.139344 9.05 0.125H3C2.20435 0.125 1.44129 0.441071 0.87868 1.00368C0.316071 1.56629 0 2.32935 0 3.125V17.125C0 17.9206 0.316071 18.6837 0.87868 19.2463C1.44129 19.8089 2.20435 20.125 3 20.125H13C13.7956 20.125 14.5587 19.8089 15.1213 19.2463C15.6839 18.6837 16 17.9206 16 17.125V7.125C16 7.125 16 7.125 16 7.065ZM10 3.535L12.59 6.125H11C10.7348 6.125 10.4804 6.01964 10.2929 5.83211C10.1054 5.64457 10 5.39022 10 5.125V3.535ZM14 17.125C14 17.3902 13.8946 17.6446 13.7071 17.8321C13.5196 18.0196 13.2652 18.125 13 18.125H3C2.73478 18.125 2.48043 18.0196 2.29289 17.8321C2.10536 17.6446 2 17.3902 2 17.125V3.125C2 2.85978 2.10536 2.60543 2.29289 2.41789C2.48043 2.23036 2.73478 2.125 3 2.125H8V5.125C8 5.92065 8.31607 6.68371 8.87868 7.24632C9.44129 7.80893 10.2044 8.125 11 8.125H14V17.125ZM8 9.125C7.73478 9.125 7.48043 9.23036 7.29289 9.41789C7.10536 9.60543 7 9.85978 7 10.125V12.125C7 12.3902 7.10536 12.6446 7.29289 12.8321C7.48043 13.0196 7.73478 13.125 8 13.125C8.26522 13.125 8.51957 13.0196 8.70711 12.8321C8.89464 12.6446 9 12.3902 9 12.125V10.125C9 9.85978 8.89464 9.60543 8.70711 9.41789C8.51957 9.23036 8.26522 9.125 8 9.125Z" fill="black"/>
20+
</svg>
21+
);

dashboard/log-analyzer/src/components/logs/LogTable.tsx

Lines changed: 111 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
import { type LogEntry } from "@/lib/types";
1212
import { useState } from 'react';
1313
import { Loader2 } from "lucide-react";
14+
import { FileIcon } from "@/components/icons";
1415
import React from 'react';
1516

1617
interface LogTableProps {
@@ -21,9 +22,10 @@ interface LogTableProps {
2122
observerRef?: (node: Element | null) => void;
2223
isLoading?: boolean;
2324
hasMore?: boolean;
25+
isLoadingMore?: boolean;
2426
}
2527

26-
export function LogTable({ logs = [], onSort, sortColumn, sortOrder, observerRef, isLoading, hasMore }: LogTableProps) {
28+
export function LogTable({ logs = [], onSort, sortColumn, sortOrder, observerRef, isLoading, hasMore, isLoadingMore }: LogTableProps) {
2729
const [hoveredColumn, setHoveredColumn] = useState<string | null>(null);
2830

2931
const renderSortIndicator = (column: string) => {
@@ -35,122 +37,116 @@ export function LogTable({ logs = [], onSort, sortColumn, sortOrder, observerRef
3537

3638
return (
3739
<div className="flex flex-col h-full">
38-
{/* Fixed header */}
39-
<div className="sticky top-0 z-10 bg-white">
40-
<Table className="border-collapse w-full table-fixed">
41-
<TableHeader className="table-header">
42-
<TableRow>
43-
<TableHead
44-
className="w-[10%] truncate"
45-
>
46-
ID
47-
</TableHead>
48-
<TableHead
49-
className="w-[120px] max-w-[120px] truncate"
50-
onClick={() => onSort('timestamp')}
51-
onMouseEnter={() => setHoveredColumn('timestamp')}
52-
onMouseLeave={() => setHoveredColumn(null)}
53-
>
54-
Time{renderSortIndicator('timestamp')}
55-
</TableHead>
56-
<TableHead
57-
className="w-[80px] max-w-[80px] truncate"
58-
>
59-
Level
60-
</TableHead>
61-
<TableHead
62-
className="w-[120px] max-w-[120px] truncate"
63-
>
64-
Service
65-
</TableHead>
66-
<TableHead
67-
className="w-[80px] max-w-[80px] truncate"
68-
>
69-
Method
70-
</TableHead>
71-
<TableHead
72-
className="w-[14%] truncate"
73-
>
74-
Path
75-
</TableHead>
76-
<TableHead
77-
className="w-[80px] max-w-[80px] truncate"
78-
>
79-
Status
80-
</TableHead>
81-
<TableHead
82-
className="w-[30%] truncate"
83-
>
84-
Message
85-
</TableHead>
86-
</TableRow>
87-
</TableHeader>
88-
</Table>
89-
</div>
90-
91-
{/* Scrollable body */}
92-
<div className="flex-1 overflow-auto min-h-0">
40+
{isLoading ? (
9341
<div className="h-full">
94-
<Table className="border-collapse w-full table-fixed">
95-
<TableBody>
96-
{logs?.map((log, index) => (
97-
<React.Fragment key={log.request_id || index}>
98-
{hasMore && index === logs.length - 6 && (
99-
<tr ref={observerRef}>
100-
<td colSpan={8} className="p-0">
101-
{isLoading && (
102-
<div className="w-full flex items-center justify-center py-4">
103-
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
104-
</div>
105-
)}
106-
</td>
107-
</tr>
108-
)}
109-
<TableRow className="table-row">
110-
<TableCell className="w-[10%] truncate">{log.request_id}</TableCell>
111-
<TableCell className="w-[120px] max-w-[120px] truncate">
112-
{new Date(log.timestamp).toLocaleDateString('en-GB', {
113-
day: '2-digit',
114-
month: '2-digit',
115-
year: '2-digit',
116-
hour: '2-digit',
117-
minute: '2-digit',
118-
second: '2-digit',
119-
hour12: false
120-
}).replace(',', '')}
121-
</TableCell>
122-
<TableCell className="w-[80px] max-w-[80px] truncate">
123-
<span className={`inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
124-
${log.level === 'ERROR' ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
125-
log.level === 'WARN' ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
126-
log.level === 'INFO' ? 'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]' :
127-
log.level === 'DEBUG' ? 'bg-[var(--bg-pill-debug)] text-[var(--text-pill-debug)]' :
128-
'bg-[var(--bg-pill-default)] text-[var(--text-pill-default)]'}`
129-
}>
130-
{log.level}
131-
</span>
132-
</TableCell>
133-
<TableCell className="w-[120px] max-w-[120px] truncate">{log.service}</TableCell>
134-
<TableCell className="w-[80px] max-w-[80px] truncate">{log.request_method}</TableCell>
135-
<TableCell className="w-[14%] truncate">{log.request_path}</TableCell>
136-
<TableCell className="w-[80px] max-w-[80px] truncate">
137-
<span className={`inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
138-
${log.status_code >= 400 ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
139-
log.status_code >= 300 ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
140-
log.status_code >= 200 ? 'bg-[var(--bg-pill-success)] text-[var(--text-pill-success)]' :
141-
'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]'}`
142-
}>
143-
{log.status_code}
144-
</span>
145-
</TableCell>
146-
<TableCell className="w-[30%] truncate">{log.message}</TableCell>
147-
</TableRow>
148-
</React.Fragment>
149-
))}
150-
</TableBody>
151-
</Table>
42+
<div className="space-y-[29px]">
43+
{[...Array(Math.ceil((window.innerHeight - 48) / 67))].map((_, index) => (
44+
<div
45+
key={index}
46+
className="h-[24px] bg-[#D9D9D9] rounded-[4px] animate-pulse"
47+
/>
48+
))}
49+
</div>
15250
</div>
153-
</div>
51+
) : (
52+
<>
53+
<div className="sticky top-0 z-10 bg-white">
54+
<Table className="border-collapse w-full table-fixed">
55+
<TableHeader className="table-header">
56+
<TableRow>
57+
<TableHead className="w-[10%] truncate">ID</TableHead>
58+
<TableHead
59+
className="w-[120px] max-w-[120px] truncate"
60+
onClick={() => onSort('timestamp')}
61+
onMouseEnter={() => setHoveredColumn('timestamp')}
62+
onMouseLeave={() => setHoveredColumn(null)}
63+
>
64+
Time{renderSortIndicator('timestamp')}
65+
</TableHead>
66+
<TableHead className="w-[80px] max-w-[80px] truncate">Level</TableHead>
67+
<TableHead className="w-[120px] max-w-[120px] truncate">Service</TableHead>
68+
<TableHead className="w-[80px] max-w-[80px] truncate">Method</TableHead>
69+
<TableHead className="w-[14%] truncate">Path</TableHead>
70+
<TableHead className="w-[80px] max-w-[80px] truncate">Status</TableHead>
71+
<TableHead className="w-[30%] truncate">Message</TableHead>
72+
</TableRow>
73+
</TableHeader>
74+
</Table>
75+
</div>
76+
77+
{logs.length === 0 ? (
78+
<div className="h-[calc(100%-24px)] flex flex-col items-center justify-center bg-[--background] rounded-lg mt-6">
79+
<div className="mb-[18px]">
80+
<FileIcon />
81+
</div>
82+
<span className="text-[#000000]">No data for the selected filter</span>
83+
</div>
84+
) : (
85+
<div className="flex-1 overflow-auto min-h-0">
86+
<div className="h-full">
87+
<Table className="border-collapse w-full table-fixed">
88+
<TableBody>
89+
{logs?.map((log, index) => (
90+
<React.Fragment key={log.request_id || index}>
91+
{hasMore && index === logs.length - 6 && (
92+
<tr ref={observerRef}>
93+
<td colSpan={8} className="p-0">
94+
{isLoadingMore && (
95+
<div className="w-full flex items-center justify-center py-4">
96+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
97+
</div>
98+
)}
99+
</td>
100+
</tr>
101+
)}
102+
<TableRow className="table-row">
103+
<TableCell className="w-[10%] truncate">{log.request_id}</TableCell>
104+
<TableCell className="w-[120px] max-w-[120px] truncate">
105+
{new Date(log.timestamp).toLocaleDateString('en-GB', {
106+
day: '2-digit',
107+
month: '2-digit',
108+
year: '2-digit',
109+
hour: '2-digit',
110+
minute: '2-digit',
111+
second: '2-digit',
112+
hour12: false
113+
}).replace(',', '')}
114+
</TableCell>
115+
<TableCell className="w-[80px] max-w-[80px] truncate">
116+
<span className={`inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
117+
${log.level === 'ERROR' ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
118+
log.level === 'WARN' ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
119+
log.level === 'INFO' ? 'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]' :
120+
log.level === 'DEBUG' ? 'bg-[var(--bg-pill-debug)] text-[var(--text-pill-debug)]' :
121+
'bg-[var(--bg-pill-default)] text-[var(--text-pill-default)]'}`
122+
}>
123+
{log.level}
124+
</span>
125+
</TableCell>
126+
<TableCell className="w-[120px] max-w-[120px] truncate">{log.service}</TableCell>
127+
<TableCell className="w-[80px] max-w-[80px] truncate">{log.request_method}</TableCell>
128+
<TableCell className="w-[14%] truncate">{log.request_path}</TableCell>
129+
<TableCell className="w-[80px] max-w-[80px] truncate">
130+
<span className={`inline-flex items-center rounded-sm px-2 py-1 text-xs font-medium
131+
${log.status_code >= 400 ? 'bg-[var(--bg-pill-error)] text-[var(--text-pill-error)]' :
132+
log.status_code >= 300 ? 'bg-[var(--bg-pill-warn)] text-[var(--text-pill-warn)]' :
133+
log.status_code >= 200 ? 'bg-[var(--bg-pill-success)] text-[var(--text-pill-success)]' :
134+
'bg-[var(--bg-pill-info)] text-[var(--text-pill-info)]'}`
135+
}>
136+
{log.status_code}
137+
</span>
138+
</TableCell>
139+
<TableCell className="w-[30%] truncate">{log.message}</TableCell>
140+
</TableRow>
141+
</React.Fragment>
142+
))}
143+
</TableBody>
144+
</Table>
145+
</div>
146+
</div>
147+
)}
148+
</>
149+
)}
154150
</div>
155151
);
156152
}

0 commit comments

Comments
 (0)