Skip to content

Commit e8c2918

Browse files
committed
feat: implement command panel
1 parent f738a42 commit e8c2918

6 files changed

Lines changed: 193 additions & 20 deletions

File tree

packages/dashboard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
},
1111
"dependencies": {
1212
"@dagrejs/dagre": "^1.1.4",
13-
"@radix-ui/react-dialog": "^1.1.1",
13+
"@radix-ui/react-dialog": "^1.1.2",
1414
"@radix-ui/react-dropdown-menu": "^2.1.1",
1515
"@radix-ui/react-icons": "^1.3.0",
1616
"@radix-ui/react-label": "^2.1.0",

packages/dashboard/src/components/layout/menu.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import DatePicker from "@/components/layout/date-picker";
44
import { MenuLink } from "@/components/layout/menu-link";
55
import { ModeToggle } from "@/components/layout/mode-toggle";
66
import { UserMenu } from "@/components/layout/user-menu";
7+
import { SearchMenu } from "@/components/layout/search-menu";
78

89
export const Menu = ({ guest = false }) => {
910
return (
@@ -33,8 +34,9 @@ export const Menu = ({ guest = false }) => {
3334
</>
3435
)}
3536
</nav>
36-
<div className="ml-auto flex items-center space-x-4">
37+
<div className="ml-auto flex items-center space-x-2">
3738
{!guest && <DatePicker />}
39+
{!guest && <SearchMenu />}
3840
{!guest ? <UserMenu /> : <ModeToggle />}
3941
</div>
4042
</div>
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import { useEffect, useMemo, useState } from "react";
2+
import { To, useNavigate } from "react-router-dom";
3+
import {
4+
AppWindowIcon,
5+
CircleXIcon,
6+
CpuIcon,
7+
SearchIcon,
8+
UsersIcon,
9+
} from "lucide-react";
10+
11+
import {
12+
CommandDialog,
13+
CommandEmpty,
14+
CommandGroup,
15+
CommandInput,
16+
CommandItem,
17+
CommandList,
18+
CommandSeparator,
19+
} from "@/components/ui/command";
20+
import { Button } from "@/components/ui/button";
21+
import { Tooltipped } from "@/components/ui/tooltipped";
22+
23+
import { useData } from "@/lib/api";
24+
25+
export function SearchMenu() {
26+
const [open, setOpen] = useState(false);
27+
const navigate = useNavigate();
28+
29+
useEffect(() => {
30+
const down = (e: KeyboardEvent) => {
31+
if (
32+
e.key === "k" &&
33+
(e.metaKey || e.ctrlKey) &&
34+
!e.shiftKey &&
35+
!e.altKey
36+
) {
37+
e.preventDefault();
38+
setOpen((open) => !open);
39+
}
40+
};
41+
42+
document.addEventListener("keydown", down);
43+
return () => document.removeEventListener("keydown", down);
44+
}, []);
45+
46+
const { data: functions } = useData(`functions`);
47+
const mappedFunctions = useMemo(() => {
48+
return functions
49+
?.map((func: any) => ({
50+
...func,
51+
lastInvocation: func.lastInvocation || "0",
52+
tags: [
53+
`Region: ${func.region}`,
54+
...Object.entries(func.tags || {})
55+
.filter(([tag]) => !tag.startsWith("aws:cloudformation:"))
56+
.filter(([tag]) => !tag.startsWith("lumigo:"))
57+
.map(([tag, value]) => {
58+
return `${tag.toLowerCase()}: ${value}`;
59+
}),
60+
],
61+
}))
62+
.sort((a, b) => (a.lastInvocation > b.lastInvocation ? -1 : 1));
63+
}, [functions]);
64+
65+
const go = (url: To) => {
66+
navigate(url);
67+
setOpen(false);
68+
};
69+
70+
return (
71+
<>
72+
<Tooltipped
73+
title={
74+
<>
75+
Search <span className="text-muted-foreground">⌘K</span>
76+
</>
77+
}
78+
>
79+
<Button variant="outline" size="icon" onClick={() => setOpen(true)}>
80+
<SearchIcon className="size-4" />
81+
</Button>
82+
</Tooltipped>
83+
<CommandDialog open={open} onOpenChange={setOpen}>
84+
<CommandInput placeholder="Search functions or commands..." />
85+
<CommandList>
86+
<CommandEmpty>No results found.</CommandEmpty>
87+
<CommandGroup heading="Suggestions">
88+
<CommandItem onSelect={() => go(`/functions`)}>
89+
<AppWindowIcon className="mr-2 h-4 w-4" />
90+
<span>View functions</span>
91+
</CommandItem>
92+
<CommandItem onSelect={() => go(`/errors`)}>
93+
<CircleXIcon className="mr-2 h-4 w-4" />
94+
<span>View errors</span>
95+
</CommandItem>
96+
<CommandItem onSelect={() => go(`/users`)}>
97+
<UsersIcon className="mr-2 h-4 w-4" />
98+
<span>View users</span>
99+
</CommandItem>
100+
</CommandGroup>
101+
<CommandSeparator />
102+
{mappedFunctions ? (
103+
<CommandGroup heading="Functions">
104+
{mappedFunctions.map(
105+
(func: { region: string; name: string; tags: string[] }) => (
106+
<CommandItem
107+
key={func.region + func.name}
108+
onSelect={() =>
109+
go(`/functions/${func.region}/${func.name}/invocations`)
110+
}
111+
>
112+
<div className="flex">
113+
<CpuIcon className="mr-2 h-4 w-4" />
114+
<div className="flex-1">
115+
<div className="truncate">{func.name}</div>
116+
<div className="text-muted-foreground text-xs truncate">
117+
{func.tags.join(", ")}
118+
</div>
119+
</div>
120+
</div>
121+
</CommandItem>
122+
),
123+
)}
124+
</CommandGroup>
125+
) : null}
126+
</CommandList>
127+
</CommandDialog>
128+
</>
129+
);
130+
}

packages/dashboard/src/components/layout/user-menu.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Moon, Sun, User } from "lucide-react";
1+
import { User } from "lucide-react";
22

33
import { Button } from "@/components/ui/button";
44
import {
@@ -8,7 +8,7 @@ import {
88
DropdownMenuTrigger,
99
DropdownMenuLabel,
1010
DropdownMenuSeparator,
11-
DropdownMenuCheckboxItem
11+
DropdownMenuCheckboxItem,
1212
} from "@/components/ui/dropdown-menu";
1313
import { useTheme } from "@/components/layout/theme-provider";
1414
import { removeToken } from "@/lib/auth";
@@ -36,13 +36,22 @@ export function UserMenu() {
3636
</DropdownMenuItem>
3737
<DropdownMenuSeparator />
3838
<DropdownMenuLabel>Theme</DropdownMenuLabel>
39-
<DropdownMenuCheckboxItem checked={theme === 'system'} onClick={() => setTheme("system")}>
39+
<DropdownMenuCheckboxItem
40+
checked={theme === "system"}
41+
onClick={() => setTheme("system")}
42+
>
4043
System
4144
</DropdownMenuCheckboxItem>
42-
<DropdownMenuCheckboxItem checked={theme === 'light'} onClick={() => setTheme("light")}>
45+
<DropdownMenuCheckboxItem
46+
checked={theme === "light"}
47+
onClick={() => setTheme("light")}
48+
>
4349
Light
4450
</DropdownMenuCheckboxItem>
45-
<DropdownMenuCheckboxItem checked={theme === 'dark'} onClick={() => setTheme("dark")}>
51+
<DropdownMenuCheckboxItem
52+
checked={theme === "dark"}
53+
onClick={() => setTheme("dark")}
54+
>
4655
Dark
4756
</DropdownMenuCheckboxItem>
4857
</DropdownMenuContent>

packages/dashboard/src/components/ui/command.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ const CommandItem = React.forwardRef<
115115
<CommandPrimitive.Item
116116
ref={ref}
117117
className={cn(
118-
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
118+
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50",
119119
className
120120
)}
121121
{...props}

yarn.lock

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5335,24 +5335,24 @@ __metadata:
53355335
languageName: node
53365336
linkType: hard
53375337

5338-
"@radix-ui/react-dialog@npm:^1.1.1":
5339-
version: 1.1.1
5340-
resolution: "@radix-ui/react-dialog@npm:1.1.1"
5338+
"@radix-ui/react-dialog@npm:^1.1.2":
5339+
version: 1.1.2
5340+
resolution: "@radix-ui/react-dialog@npm:1.1.2"
53415341
dependencies:
53425342
"@radix-ui/primitive": "npm:1.1.0"
53435343
"@radix-ui/react-compose-refs": "npm:1.1.0"
5344-
"@radix-ui/react-context": "npm:1.1.0"
5345-
"@radix-ui/react-dismissable-layer": "npm:1.1.0"
5346-
"@radix-ui/react-focus-guards": "npm:1.1.0"
5344+
"@radix-ui/react-context": "npm:1.1.1"
5345+
"@radix-ui/react-dismissable-layer": "npm:1.1.1"
5346+
"@radix-ui/react-focus-guards": "npm:1.1.1"
53475347
"@radix-ui/react-focus-scope": "npm:1.1.0"
53485348
"@radix-ui/react-id": "npm:1.1.0"
5349-
"@radix-ui/react-portal": "npm:1.1.1"
5350-
"@radix-ui/react-presence": "npm:1.1.0"
5349+
"@radix-ui/react-portal": "npm:1.1.2"
5350+
"@radix-ui/react-presence": "npm:1.1.1"
53515351
"@radix-ui/react-primitive": "npm:2.0.0"
53525352
"@radix-ui/react-slot": "npm:1.1.0"
53535353
"@radix-ui/react-use-controllable-state": "npm:1.1.0"
53545354
aria-hidden: "npm:^1.1.1"
5355-
react-remove-scroll: "npm:2.5.7"
5355+
react-remove-scroll: "npm:2.6.0"
53565356
peerDependencies:
53575357
"@types/react": "*"
53585358
"@types/react-dom": "*"
@@ -5363,7 +5363,7 @@ __metadata:
53635363
optional: true
53645364
"@types/react-dom":
53655365
optional: true
5366-
checksum: 10c0/a21e318e8d45bed22067880f66beb4ea91118a6c0d43aa20de495c0373b53c12dfe28f58196d5b33300573a5e24e064ec53648a576f02366fb5a297d887b0860
5366+
checksum: 10c0/61997c23605ff604ef1673480eea0b63cbe2e102d24e64b71431afa408bfdda26f879193c09254304eb17a8d623085a2e6c96b5c944658c02bd935f8cf0f9546
53675367
languageName: node
53685368
linkType: hard
53695369

@@ -5503,6 +5503,19 @@ __metadata:
55035503
languageName: node
55045504
linkType: hard
55055505

5506+
"@radix-ui/react-focus-guards@npm:1.1.1":
5507+
version: 1.1.1
5508+
resolution: "@radix-ui/react-focus-guards@npm:1.1.1"
5509+
peerDependencies:
5510+
"@types/react": "*"
5511+
react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc
5512+
peerDependenciesMeta:
5513+
"@types/react":
5514+
optional: true
5515+
checksum: 10c0/2e99750ca593083a530542a185d656b45b100752353a7a193a67566e3c256414a76fa9171d152f8c0167b8d6c1fdf62b2e07750d7af2974bf8ef39eb204aa537
5516+
languageName: node
5517+
linkType: hard
5518+
55065519
"@radix-ui/react-focus-scope@npm:1.0.4":
55075520
version: 1.0.4
55085521
resolution: "@radix-ui/react-focus-scope@npm:1.0.4"
@@ -8470,7 +8483,7 @@ __metadata:
84708483
resolution: "@trace-stack/dashboard@workspace:packages/dashboard"
84718484
dependencies:
84728485
"@dagrejs/dagre": "npm:^1.1.4"
8473-
"@radix-ui/react-dialog": "npm:^1.1.1"
8486+
"@radix-ui/react-dialog": "npm:^1.1.2"
84748487
"@radix-ui/react-dropdown-menu": "npm:^2.1.1"
84758488
"@radix-ui/react-icons": "npm:^1.3.0"
84768489
"@radix-ui/react-label": "npm:^2.1.0"
@@ -18865,7 +18878,7 @@ __metadata:
1886518878
languageName: node
1886618879
linkType: hard
1886718880

18868-
"react-remove-scroll-bar@npm:^2.3.3, react-remove-scroll-bar@npm:^2.3.4":
18881+
"react-remove-scroll-bar@npm:^2.3.3, react-remove-scroll-bar@npm:^2.3.4, react-remove-scroll-bar@npm:^2.3.6":
1886918882
version: 2.3.6
1887018883
resolution: "react-remove-scroll-bar@npm:2.3.6"
1887118884
dependencies:
@@ -18919,6 +18932,25 @@ __metadata:
1891918932
languageName: node
1892018933
linkType: hard
1892118934

18935+
"react-remove-scroll@npm:2.6.0":
18936+
version: 2.6.0
18937+
resolution: "react-remove-scroll@npm:2.6.0"
18938+
dependencies:
18939+
react-remove-scroll-bar: "npm:^2.3.6"
18940+
react-style-singleton: "npm:^2.2.1"
18941+
tslib: "npm:^2.1.0"
18942+
use-callback-ref: "npm:^1.3.0"
18943+
use-sidecar: "npm:^1.1.2"
18944+
peerDependencies:
18945+
"@types/react": ^16.8.0 || ^17.0.0 || ^18.0.0
18946+
react: ^16.8.0 || ^17.0.0 || ^18.0.0
18947+
peerDependenciesMeta:
18948+
"@types/react":
18949+
optional: true
18950+
checksum: 10c0/c5881c537477d986e8d25d2588a9b6f7fe1254e05946fb4f4b55baeead502b0e1875fc3c42bb6f82736772cd96a50266e41d84e3c4cd25e9525bdfe2d838e96d
18951+
languageName: node
18952+
linkType: hard
18953+
1892218954
"react-router-dom@npm:^6.26.1":
1892318955
version: 6.26.1
1892418956
resolution: "react-router-dom@npm:6.26.1"

0 commit comments

Comments
 (0)