Skip to content

Commit 54a6d07

Browse files
committed
feat: add create/delete/edit/filter support to jobs, queues, and pods
Signed-off-by: Deep <[email protected]>
1 parent a684f6a commit 54a6d07

File tree

16 files changed

+2242
-266
lines changed

16 files changed

+2242
-266
lines changed

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@radix-ui/react-scroll-area": "^1.2.3",
1818
"@radix-ui/react-select": "^2.2.5",
1919
"@radix-ui/react-slot": "^1.1.2",
20+
"@radix-ui/react-tooltip": "^1.2.8",
2021
"@tanstack/react-table": "^8.21.2",
2122
"@types/js-yaml": "^4.0.9",
2223
"@volcano/trpc": "^1.30.0",

apps/web/src/components/(dashboard)/jobs/columns.tsx

Lines changed: 242 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -11,108 +11,258 @@ import {
1111
DropdownMenuTrigger,
1212
} from "@/components/ui/dropdown-menu"
1313
import { ColumnDef } from "@tanstack/react-table"
14-
import { ArrowUpDown, Filter } from 'lucide-react'
14+
import { ArrowUpDown, Edit, Filter, Trash2 } from 'lucide-react'
1515
import { JobStatus } from "./jobs-management"
1616

17-
export const columns: ColumnDef<JobStatus>[] = [
18-
{
19-
accessorKey: "name",
20-
header: ({ column }) => (
21-
<Button
22-
variant="ghost"
23-
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
24-
>
25-
Name
26-
<ArrowUpDown className="ml-2 h-4 w-4" />
27-
</Button>
28-
),
29-
},
30-
{
31-
accessorKey: "namespace",
32-
header: "Namespace",
33-
},
34-
{
35-
accessorKey: "queue",
36-
header: "Queue",
37-
cell: ({ row }) => {
38-
const queue = row.getValue("queue") as string;
39-
return (
40-
<Badge
41-
className="bg-blue-100 text-blue-800 hover:bg-blue-100/60 "
17+
interface CreateColumnsOptions {
18+
availableNamespaces: string[]
19+
availableQueues: string[]
20+
availableStatuses: string[]
21+
onEdit?: (job: JobStatus) => void
22+
onDelete?: (job: JobStatus) => void
23+
}
24+
25+
export const createColumns = ({
26+
availableNamespaces,
27+
availableQueues,
28+
availableStatuses,
29+
onEdit,
30+
onDelete
31+
}: CreateColumnsOptions): ColumnDef<JobStatus>[] => [
32+
{
33+
accessorKey: "name",
34+
header: ({ column }) => (
35+
<Button
36+
variant="ghost"
37+
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
4238
>
43-
{queue.charAt(0).toUpperCase() + queue.slice(1)}
44-
</Badge>
45-
);
39+
Name
40+
<ArrowUpDown className="ml-2 h-4 w-4" />
41+
</Button>
42+
),
4643
},
47-
},
48-
{
49-
accessorKey: "createdAt",
50-
header: ({ column }) => (
51-
<Button
52-
variant="ghost"
53-
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
54-
>
55-
Creation Time
56-
<ArrowUpDown className="ml-2 h-4 w-4" />
57-
</Button>
58-
),
59-
cell: ({ row }) => {
60-
const createdAt = row.getValue("createdAt") as Date
61-
return new Intl.DateTimeFormat("en-US", {
62-
dateStyle: "medium",
63-
timeStyle: "short",
64-
}).format(createdAt)
44+
{
45+
accessorKey: "namespace",
46+
header: ({ column }) => (
47+
<div className="flex items-center">
48+
Namespace
49+
<DropdownMenu>
50+
<DropdownMenuTrigger asChild>
51+
<Button variant="ghost" size="sm" className="ml-2 h-8 p-1">
52+
<Filter className="h-4 w-4" />
53+
</Button>
54+
</DropdownMenuTrigger>
55+
<DropdownMenuContent align="start">
56+
<DropdownMenuLabel>Filter by Namespace</DropdownMenuLabel>
57+
<DropdownMenuSeparator />
58+
<DropdownMenuCheckboxItem
59+
checked={!column.getFilterValue()}
60+
onCheckedChange={(checked) => {
61+
if (checked) column.setFilterValue(undefined)
62+
}}
63+
>
64+
All
65+
</DropdownMenuCheckboxItem>
66+
{availableNamespaces.map((namespace) => (
67+
<DropdownMenuCheckboxItem
68+
key={namespace}
69+
checked={column.getFilterValue() === namespace}
70+
onCheckedChange={(checked) => {
71+
column.setFilterValue(checked ? namespace : undefined)
72+
}}
73+
>
74+
{namespace}
75+
</DropdownMenuCheckboxItem>
76+
))}
77+
</DropdownMenuContent>
78+
</DropdownMenu>
79+
</div>
80+
),
81+
cell: ({ row }) => {
82+
const namespace = row.getValue("namespace") as string;
83+
return (
84+
<Badge
85+
className="bg-purple-100 text-purple-800 hover:bg-purple-100/60"
86+
>
87+
{namespace}
88+
</Badge>
89+
);
90+
},
91+
filterFn: (row, columnId, filterValue) => {
92+
return row.getValue(columnId) === filterValue
93+
},
6594
},
66-
},
67-
{
68-
accessorKey: "status",
69-
header: ({ column }) => (
70-
<div className="flex items-center">
71-
Status
72-
<DropdownMenu>
73-
<DropdownMenuTrigger asChild>
74-
<Button variant="ghost" size="sm" className="ml-2 h-8 p-1">
75-
<Filter className="h-4 w-4" />
76-
</Button>
77-
</DropdownMenuTrigger>
78-
<DropdownMenuContent align="start">
79-
<DropdownMenuLabel>Filter by Status</DropdownMenuLabel>
80-
<DropdownMenuSeparator />
81-
{["pending", "running", "completed", "failed"].map((status) => (
95+
{
96+
accessorKey: "queue",
97+
header: ({ column }) => (
98+
<div className="flex items-center">
99+
Queue
100+
<DropdownMenu>
101+
<DropdownMenuTrigger asChild>
102+
<Button variant="ghost" size="sm" className="ml-2 h-8 p-1">
103+
<Filter className="h-4 w-4" />
104+
</Button>
105+
</DropdownMenuTrigger>
106+
<DropdownMenuContent align="start">
107+
<DropdownMenuLabel>Filter by Queue</DropdownMenuLabel>
108+
<DropdownMenuSeparator />
82109
<DropdownMenuCheckboxItem
83-
key={status}
84-
checked={column.getFilterValue() === status}
110+
checked={!column.getFilterValue()}
85111
onCheckedChange={(checked) => {
86-
column.setFilterValue(checked ? status : undefined)
112+
if (checked) column.setFilterValue(undefined)
87113
}}
88114
>
89-
{status.charAt(0).toUpperCase() + status.slice(1)}
115+
All
90116
</DropdownMenuCheckboxItem>
91-
))}
92-
</DropdownMenuContent>
93-
</DropdownMenu>
94-
</div>
95-
),
96-
cell: ({ row }) => {
97-
const status = row.getValue("status") as string
98-
return (
99-
<Badge
100-
className={
101-
status === "completed"
102-
? "bg-green-100 text-green-800"
103-
: status === "running"
104-
? "bg-blue-100 text-blue-800"
105-
: status === "pending"
106-
? "bg-yellow-100 text-yellow-800"
107-
: "bg-red-100 text-red-800"
108-
}
117+
{availableQueues.map((queue) => (
118+
<DropdownMenuCheckboxItem
119+
key={queue}
120+
checked={column.getFilterValue() === queue}
121+
onCheckedChange={(checked) => {
122+
column.setFilterValue(checked ? queue : undefined)
123+
}}
124+
>
125+
{queue}
126+
</DropdownMenuCheckboxItem>
127+
))}
128+
</DropdownMenuContent>
129+
</DropdownMenu>
130+
</div>
131+
),
132+
cell: ({ row }) => {
133+
const queue = row.getValue("queue") as string;
134+
return (
135+
<Badge
136+
className="bg-blue-100 text-blue-800 hover:bg-blue-100/60 "
137+
>
138+
{queue}
139+
</Badge>
140+
);
141+
},
142+
filterFn: (row, columnId, filterValue) => {
143+
return row.getValue(columnId) === filterValue
144+
},
145+
},
146+
{
147+
accessorKey: "createdAt",
148+
header: ({ column }) => (
149+
<Button
150+
variant="ghost"
151+
onClick={() => column.toggleSorting(column.getIsSorted() === "asc")}
109152
>
110-
{status}
111-
</Badge>
112-
)
153+
Creation Time
154+
<ArrowUpDown className="ml-2 h-4 w-4" />
155+
</Button>
156+
),
157+
cell: ({ row }) => {
158+
const createdAt = row.getValue("createdAt") as Date
159+
return new Intl.DateTimeFormat("en-US", {
160+
dateStyle: "medium",
161+
timeStyle: "short",
162+
}).format(createdAt)
163+
},
113164
},
114-
filterFn: (row, columnId, filterValue) => {
115-
return row.getValue(columnId) === filterValue
165+
{
166+
accessorKey: "status",
167+
header: ({ column }) => (
168+
<div className="flex items-center">
169+
Status
170+
<DropdownMenu>
171+
<DropdownMenuTrigger asChild>
172+
<Button variant="ghost" size="sm" className="ml-2 h-8 p-1">
173+
<Filter className="h-4 w-4" />
174+
</Button>
175+
</DropdownMenuTrigger>
176+
<DropdownMenuContent align="start">
177+
<DropdownMenuLabel>Filter by Status</DropdownMenuLabel>
178+
<DropdownMenuSeparator />
179+
<DropdownMenuCheckboxItem
180+
checked={!column.getFilterValue()}
181+
onCheckedChange={(checked) => {
182+
if (checked) column.setFilterValue(undefined)
183+
}}
184+
>
185+
All
186+
</DropdownMenuCheckboxItem>
187+
{availableStatuses.map((status) => (
188+
<DropdownMenuCheckboxItem
189+
key={status}
190+
checked={column.getFilterValue() === status}
191+
onCheckedChange={(checked) => {
192+
column.setFilterValue(checked ? status : undefined)
193+
}}
194+
>
195+
{status.charAt(0).toUpperCase() + status.slice(1)}
196+
</DropdownMenuCheckboxItem>
197+
))}
198+
</DropdownMenuContent>
199+
</DropdownMenu>
200+
</div>
201+
),
202+
cell: ({ row }) => {
203+
const status = row.getValue("status") as string
204+
return (
205+
<Badge
206+
className={
207+
status === "completed"
208+
? "bg-green-100 text-green-800"
209+
: status === "running"
210+
? "bg-blue-100 text-blue-800"
211+
: status === "pending"
212+
? "bg-yellow-100 text-yellow-800"
213+
: "bg-red-100 text-red-800"
214+
}
215+
>
216+
{status}
217+
</Badge>
218+
)
219+
},
220+
filterFn: (row, columnId, filterValue) => {
221+
return row.getValue(columnId) === filterValue
222+
},
116223
},
117-
},
118-
]
224+
{
225+
id: "actions",
226+
header: "Actions",
227+
cell: ({ row }) => {
228+
const job = row.original
229+
230+
return (
231+
<div className="flex items-center gap-2">
232+
{onEdit && (
233+
<Button
234+
variant="ghost"
235+
size="sm"
236+
onClick={(e) => {
237+
e.stopPropagation()
238+
onEdit(job)
239+
}}
240+
className="h-8 w-8 p-0"
241+
>
242+
<Edit className="h-4 w-4" />
243+
</Button>
244+
)}
245+
{onDelete && (
246+
<Button
247+
variant="ghost"
248+
size="sm"
249+
onClick={(e) => {
250+
e.stopPropagation()
251+
onDelete(job)
252+
}}
253+
className="h-8 w-8 p-0 text-red-600 hover:text-red-700 hover:bg-red-50"
254+
>
255+
<Trash2 className="h-4 w-4" />
256+
</Button>
257+
)}
258+
</div>
259+
)
260+
},
261+
},
262+
]
263+
264+
export const columns = createColumns({
265+
availableNamespaces: [],
266+
availableQueues: [],
267+
availableStatuses: []
268+
})

0 commit comments

Comments
 (0)