Skip to content

Commit 02fe854

Browse files
committed
feat(frontend): restructure routes, add shared filters and DAG layers
1 parent 1008d45 commit 02fe854

27 files changed

Lines changed: 993 additions & 431 deletions

frontend/app/src/lib/api/client.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,8 +238,17 @@ export const plots = {
238238

239239
// Runs API
240240
export const runs = {
241-
async list(skip = 0, limit = 100): Promise<PaginatedResponse<RunSummary>> {
241+
async list(
242+
skip = 0,
243+
limit = 100,
244+
filters?: { statuses?: string[]; workflows?: string[]; owners?: string[]; git_refs?: string[]; configfiles?: string[]; backends?: string[] }
245+
): Promise<PaginatedResponse<RunSummary>> {
242246
const params = new URLSearchParams({ skip: String(skip), limit: String(limit) });
247+
if (filters) {
248+
for (const [key, values] of Object.entries(filters)) {
249+
values?.forEach((v: string) => params.append(key, v));
250+
}
251+
}
243252
return request<PaginatedResponse<RunSummary>>(`/runs/?${params}`);
244253
},
245254
async get(id: string): Promise<Run> {

frontend/app/src/lib/components/AppSidebar.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
import Badge from '$lib/components/ui/badge/badge.svelte';
1313
1414
// Check if we're on the network detail page
15-
const isNetworkPage = $derived($page.url.pathname === '/network');
15+
const isNetworkPage = $derived($page.url.pathname === '/database/network');
1616
1717
// Version info
1818
interface VersionData {
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
<script lang="ts">
2+
import { Search, X, SlidersHorizontal } from 'lucide-svelte';
3+
import { Input } from '$lib/components/ui/input';
4+
import { Button } from '$lib/components/ui/button';
5+
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
6+
import { FilterDialog } from '$lib/components/ui/filter-dialog';
7+
import type { FilterState, FilterCategory } from '$lib/components/ui/filter-dialog';
8+
import type { ColumnDef, VisibilityState } from '@tanstack/table-core';
9+
10+
type ColumnWithAccessor = ColumnDef<unknown, unknown> & { accessorKey?: string };
11+
12+
interface FilterBarProps {
13+
filterCategories?: FilterCategory[];
14+
filters?: FilterState;
15+
onFilterChange?: (filters: FilterState) => void;
16+
search?: string;
17+
onSearchChange?: (search: string) => void;
18+
columns?: ColumnWithAccessor[];
19+
columnVisibility?: VisibilityState;
20+
onColumnVisibilityChange?: (visibility: VisibilityState) => void;
21+
}
22+
23+
let {
24+
filterCategories = [],
25+
filters = {},
26+
onFilterChange,
27+
search,
28+
onSearchChange,
29+
columns = [],
30+
columnVisibility = {},
31+
onColumnVisibilityChange
32+
}: FilterBarProps = $props();
33+
34+
function handleSearchInput(e: Event) {
35+
onSearchChange?.((e.target as HTMLInputElement).value);
36+
}
37+
38+
function clearSearch() {
39+
onSearchChange?.('');
40+
}
41+
42+
function toggleColumn(columnId: string, checked: boolean) {
43+
onColumnVisibilityChange?.({
44+
...columnVisibility,
45+
[columnId]: checked
46+
});
47+
}
48+
</script>
49+
50+
<div class="flex items-center gap-2 mb-6 flex-wrap">
51+
<!-- Filter Dialog -->
52+
<FilterDialog
53+
categories={filterCategories}
54+
{filters}
55+
onFilterChange={(newFilters: FilterState) => onFilterChange?.(newFilters)}
56+
/>
57+
58+
<!-- Search (optional) -->
59+
{#if onSearchChange !== undefined}
60+
<div class="relative w-64">
61+
<Search class="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
62+
<Input
63+
type="text"
64+
placeholder="Search..."
65+
value={search ?? ''}
66+
oninput={handleSearchInput}
67+
class="h-8 pl-9 pr-8 text-sm"
68+
/>
69+
{#if search}
70+
<button
71+
type="button"
72+
class="absolute right-2 top-1/2 -translate-y-1/2 p-0.5 hover:bg-muted rounded"
73+
onclick={clearSearch}
74+
>
75+
<X class="h-3 w-3 text-muted-foreground" />
76+
</button>
77+
{/if}
78+
</div>
79+
{/if}
80+
81+
<!-- Spacer -->
82+
<div class="flex-1"></div>
83+
84+
<!-- Column Visibility -->
85+
<DropdownMenu.Root>
86+
<DropdownMenu.Trigger>
87+
{#snippet child({ props }: { props: Record<string, unknown> })}
88+
<Button variant="ghost" size="icon" class="h-8 w-8" {...props}>
89+
<SlidersHorizontal class="h-4 w-4" />
90+
</Button>
91+
{/snippet}
92+
</DropdownMenu.Trigger>
93+
<DropdownMenu.Content align="end">
94+
<DropdownMenu.Label>Toggle Columns</DropdownMenu.Label>
95+
<DropdownMenu.Separator />
96+
{#each columns as column}
97+
{#if (column.accessorKey || column.id) && column.header}
98+
{@const columnId = (column.id || column.accessorKey) as string}
99+
{@const isVisible = columnVisibility[columnId] !== false}
100+
<DropdownMenu.CheckboxItem
101+
checked={isVisible}
102+
onCheckedChange={(checked: boolean) => toggleColumn(columnId, checked)}
103+
>
104+
{column.header}
105+
</DropdownMenu.CheckboxItem>
106+
{/if}
107+
{/each}
108+
</DropdownMenu.Content>
109+
</DropdownMenu.Root>
110+
</div>

frontend/app/src/lib/components/Navbar.svelte

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -103,10 +103,6 @@
103103
if (path === '/' || path === '/networks') {
104104
return currentPath === path;
105105
}
106-
// For /network path, check exact match or with query params
107-
if (path === '/network') {
108-
return currentPath === '/network' || currentPath.startsWith('/network?');
109-
}
110106
// For other paths, check if current path starts with the link path
111107
return currentPath.startsWith(path);
112108
}
@@ -141,13 +137,7 @@
141137
href="/database"
142138
class="px-4 py-2 rounded-lg transition-colors duration-200 nav-link {isActive('/database') ? 'nav-link-active' : ''}"
143139
>
144-
Database
145-
</a>
146-
<a
147-
href="/network"
148-
class="px-4 py-2 rounded-lg transition-colors duration-200 nav-link {isActive('/network') ? 'nav-link-active' : ''}"
149-
>
150-
Network
140+
Networks
151141
</a>
152142
</div>
153143

frontend/app/src/lib/components/sidebar/NavMain.svelte

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { page } from '$app/stores';
3-
import { Home, Database, Network, Play } from 'lucide-svelte';
3+
import { Home, Database, Play } from 'lucide-svelte';
44
import * as Sidebar from '$lib/components/ui/sidebar';
55
66
const navItems = [
@@ -10,15 +10,10 @@
1010
icon: Home
1111
},
1212
{
13-
title: 'Database',
13+
title: 'Networks',
1414
url: '/database',
1515
icon: Database
1616
},
17-
{
18-
title: 'Network',
19-
url: '/network',
20-
icon: Network
21-
},
2217
{
2318
title: 'Runs',
2419
url: '/runs',
@@ -36,7 +31,7 @@
3631
<Sidebar.Menu>
3732
{#each navItems as item}
3833
<Sidebar.MenuItem>
39-
<Sidebar.MenuButton tooltipContent={item.title} isActive={currentPath === item.url}>
34+
<Sidebar.MenuButton tooltipContent={item.title} isActive={currentPath === item.url || (item.url !== '/' && currentPath.startsWith(item.url + '/'))}>
4035
{#snippet child({ props }: { props: Record<string, unknown> })}
4136
<a href={item.url} {...props}>
4237
<item.icon />

frontend/app/src/lib/components/sidebar/NavNetworksList.svelte

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,14 +28,14 @@
2828
// Update URL with all selected network IDs
2929
const ids = Array.from($selectedNetworkIds);
3030
if (ids.length > 0) {
31-
goto(`/network?ids=${ids.join(',')}`);
31+
goto(`/database/network?ids=${ids.join(',')}`);
3232
} else {
33-
goto('/network');
33+
goto('/database/network');
3434
}
3535
} else {
3636
// Select single network
3737
selectNetwork(networkId);
38-
goto(`/network?id=${networkId}`);
38+
goto(`/database/network?id=${networkId}`);
3939
}
4040
}
4141
@@ -44,15 +44,15 @@
4444
// Update URL with all selected network IDs
4545
const ids = Array.from($selectedNetworkIds);
4646
if (ids.length > 0) {
47-
goto(`/network?ids=${ids.join(',')}`);
47+
goto(`/database/network?ids=${ids.join(',')}`);
4848
} else {
49-
goto('/network');
49+
goto('/database/network');
5050
}
5151
}
5252
5353
function handleClearSelection() {
5454
clearSelection();
55-
goto('/network');
55+
goto('/database/network');
5656
}
5757
</script>
5858

@@ -99,7 +99,7 @@
9999
{#snippet child({ props }: { props: Record<string, unknown> })}
100100
<a
101101
{...props}
102-
href="/network?id={network.id}"
102+
href="/database/network?id={network.id}"
103103
onclick={(e) => {
104104
e.preventDefault();
105105
handleNetworkClick(network.id, e);

0 commit comments

Comments
 (0)