Skip to content

Commit c3084e4

Browse files
committed
feat: add realtime event filtering and workflow controls
1 parent 908be07 commit c3084e4

7 files changed

Lines changed: 412 additions & 22 deletions

File tree

web/src/app/(dashboard)/layout.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { ReactNode } from "react"
22
import { AppShell } from "@/components/shell/app-shell"
3+
import { CommandMenu } from "@/components/cmdk/command-menu"
34

45
export default function DashboardLayout({
56
children,
67
}: {
78
children: ReactNode
89
}) {
9-
return <AppShell>{children}</AppShell>
10+
return <AppShell>{children}
11+
<CommandMenu />
12+
</AppShell>
1013
}

web/src/app/globals.css

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,31 @@
347347

348348
::-webkit-scrollbar-thumb:hover {
349349
background-color: hsl(var(--primary) / 0.7);
350+
}
351+
352+
353+
354+
355+
356+
.command-item {
357+
display: flex;
358+
align-items: center;
359+
gap: 0.75rem;
360+
361+
padding: 0.75rem;
362+
border-radius: 0.75rem;
363+
364+
font-size: 0.875rem;
365+
366+
cursor: pointer;
367+
368+
transition: all 0.15s ease;
369+
}
370+
371+
.command-item:hover {
372+
background: var(--surface-2);
373+
}
374+
375+
.command-item[data-selected="true"] {
376+
background: var(--surface-2);
350377
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { create } from "zustand"
2+
3+
type CommandStore = {
4+
open: boolean
5+
6+
setOpen: (
7+
open: boolean
8+
) => void
9+
10+
toggle: () => void
11+
}
12+
13+
export const useCommandStore =
14+
create<CommandStore>(
15+
(set) => ({
16+
open: false,
17+
18+
setOpen: (open) =>
19+
set({
20+
open,
21+
}),
22+
23+
toggle: () =>
24+
set((state) => ({
25+
open: !state.open,
26+
})),
27+
})
28+
)
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
"use client"
2+
3+
import {
4+
Command,
5+
CommandGroup,
6+
CommandInput,
7+
CommandItem,
8+
CommandList,
9+
CommandSeparator,
10+
} from "cmdk"
11+
12+
import {
13+
Activity,
14+
AlertTriangle,
15+
BarChart3,
16+
Boxes,
17+
Cable,
18+
Radio,
19+
} from "lucide-react"
20+
21+
import { useRouter } from "next/navigation"
22+
23+
import { useCommandStore } from "@/app/stores/command-store"
24+
25+
import { useCommandMenu } from "@/hooks/use-command-menu"
26+
27+
export function CommandMenu() {
28+
useCommandMenu()
29+
30+
const router = useRouter()
31+
32+
const open =
33+
useCommandStore(
34+
(s) => s.open
35+
)
36+
37+
const setOpen =
38+
useCommandStore(
39+
(s) => s.setOpen
40+
)
41+
42+
return (
43+
<>
44+
{open && (
45+
<div
46+
className="
47+
fixed inset-0 z-[100]
48+
bg-black/50
49+
backdrop-blur-sm
50+
"
51+
onClick={() =>
52+
setOpen(false)
53+
}
54+
/>
55+
)}
56+
57+
<div
58+
className={`
59+
fixed left-1/2 top-[18%]
60+
z-[101]
61+
w-full max-w-2xl
62+
-translate-x-1/2
63+
transition-all duration-200
64+
${
65+
open
66+
? "opacity-100 scale-100"
67+
: "pointer-events-none opacity-0 scale-95"
68+
}
69+
`}
70+
>
71+
72+
<Command
73+
className="
74+
overflow-hidden rounded-2xl
75+
border border-border
76+
bg-background
77+
shadow-2xl
78+
"
79+
>
80+
81+
<CommandInput
82+
placeholder="Search commands..."
83+
className="
84+
h-14 w-full border-b
85+
border-border bg-transparent
86+
px-4 text-sm outline-none
87+
"
88+
/>
89+
90+
<CommandList className="max-h-[420px] overflow-y-auto p-2">
91+
92+
<CommandGroup heading="Navigation">
93+
94+
<CommandItem
95+
onSelect={() => {
96+
router.push("/events")
97+
setOpen(false)
98+
}}
99+
className="command-item"
100+
>
101+
<Activity className="h-4 w-4" />
102+
Events
103+
</CommandItem>
104+
105+
<CommandItem
106+
onSelect={() => {
107+
router.push("/metrics")
108+
setOpen(false)
109+
}}
110+
className="command-item"
111+
>
112+
<BarChart3 className="h-4 w-4" />
113+
Metrics
114+
</CommandItem>
115+
116+
<CommandItem
117+
onSelect={() => {
118+
router.push("/dlq")
119+
setOpen(false)
120+
}}
121+
className="command-item"
122+
>
123+
<AlertTriangle className="h-4 w-4" />
124+
Dead Letter Queue
125+
</CommandItem>
126+
127+
<CommandItem
128+
onSelect={() => {
129+
router.push("/integrations")
130+
setOpen(false)
131+
}}
132+
className="command-item"
133+
>
134+
<Cable className="h-4 w-4" />
135+
Integrations
136+
</CommandItem>
137+
138+
<CommandItem
139+
onSelect={() => {
140+
router.push("/streams")
141+
setOpen(false)
142+
}}
143+
className="command-item"
144+
>
145+
<Radio className="h-4 w-4" />
146+
Streams
147+
</CommandItem>
148+
149+
<CommandItem
150+
onSelect={() => {
151+
router.push("/delivery-targets")
152+
setOpen(false)
153+
}}
154+
className="command-item"
155+
>
156+
<Boxes className="h-4 w-4" />
157+
Delivery Targets
158+
</CommandItem>
159+
160+
</CommandGroup>
161+
162+
<CommandSeparator />
163+
164+
</CommandList>
165+
166+
</Command>
167+
</div>
168+
</>
169+
)
170+
}

web/src/components/events/event-toolbar.tsx

Lines changed: 91 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,38 @@ import {
7878
Play,
7979
Search,
8080
Wifi,
81+
Funnel
8182
} from "lucide-react"
8283

8384
import { useEventsStore } from "@/app/stores/events-store"
8485

8586
type Props = {
8687
count: number
88+
8789
query: string
88-
setQuery: React.Dispatch<React.SetStateAction<string>>
90+
setQuery: React.Dispatch<
91+
React.SetStateAction<string>
92+
>
93+
94+
status: string
95+
setStatus: React.Dispatch<
96+
React.SetStateAction<string>
97+
>
98+
99+
provider: string
100+
setProvider: React.Dispatch<
101+
React.SetStateAction<string>
102+
>
89103
}
90104

91105
export function EventToolbar({
92106
count,
93107
query,
94108
setQuery,
109+
status,
110+
setStatus,
111+
provider,
112+
setProvider,
95113
}: Props) {
96114
const paused =
97115
useEventsStore(
@@ -131,6 +149,78 @@ export function EventToolbar({
131149
</div>
132150

133151
{/* Right */}
152+
153+
{/* Status Filter */}
154+
<select
155+
value={status}
156+
onChange={(e) =>
157+
setStatus(e.target.value)
158+
}
159+
className="
160+
rounded-lg border border-border
161+
bg-surface-1 px-3 py-2
162+
text-sm text-foreground
163+
outline-none
164+
"
165+
>
166+
<option value="">
167+
All Status
168+
</option>
169+
170+
<option value="delivered">
171+
Delivered
172+
</option>
173+
174+
<option value="failed">
175+
Failed
176+
</option>
177+
178+
<option value="pending">
179+
Pending
180+
</option>
181+
182+
<option value="retrying">
183+
Retrying
184+
</option>
185+
</select>
186+
187+
{/* Provider Filter */}
188+
<select
189+
value={provider}
190+
onChange={(e) =>
191+
setProvider(e.target.value)
192+
}
193+
className="
194+
rounded-lg border border-border
195+
bg-surface-1 px-3 py-2
196+
text-sm text-foreground
197+
outline-none
198+
"
199+
>
200+
<option value="">
201+
All Providers
202+
</option>
203+
204+
<option value="github">
205+
GitHub
206+
</option>
207+
208+
<option value="stripe">
209+
Stripe
210+
</option>
211+
212+
<option value="slack">
213+
Slack
214+
</option>
215+
216+
<option value="discord">
217+
Discord
218+
</option>
219+
220+
<option value="notion">
221+
Notion
222+
</option>
223+
</select>
134224
<div className="flex items-center gap-3">
135225

136226
{/* Search */}

0 commit comments

Comments
 (0)