Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3c1bc9a
feat: add shared `TableToolbar` component for table view pages
royendo Apr 2, 2026
bc8ced5
refactor: redesign `TableToolbar` to match updated specs
royendo Apr 2, 2026
0ccd11c
fix: resolve infinite loop in `ResourceTableToolbar` effects
royendo Apr 2, 2026
8dae5bf
fix: filter dropdown context error and search input binding
royendo Apr 2, 2026
f0cdb02
fix: switch search to callback pattern for reliable state propagation
royendo Apr 2, 2026
59d9882
fix: remove prop shadow in `TableToolbar` search handler
royendo Apr 2, 2026
b09fcb2
fix: revert alerts/reports, polish `TableToolbar` UI
royendo Apr 2, 2026
0182467
fix: applied filters layout and toolbar children slot
royendo Apr 2, 2026
a7a8fb6
fix: use Rill `FilterOutlined` icon, add section gap-y-2
royendo Apr 2, 2026
3990f06
fix: always show divider, clean filter SVG, reduce env vars gap
royendo Apr 2, 2026
770d639
prettier
royendo Apr 2, 2026
4351c42
fix: use `bg-surface-background` for filter pills instead of hardcode…
royendo Apr 2, 2026
47d815d
refactor: code review fixes for `TableToolbar` system
royendo Apr 2, 2026
533800b
fix: borderless search icon, revert sort toggle, remove default sort …
royendo Apr 2, 2026
7eae959
feat: add `externalSort` prop to BasicTable to hide sort arrows
royendo Apr 2, 2026
cbd75a0
fix: clear column sort arrows when toolbar sort changes
royendo Apr 2, 2026
798257d
+ New key
royendo Apr 3, 2026
f64c721
prettier
royendo Apr 3, 2026
ed1f805
Merge branch 'main' into royendo/add-shared-table-toolbar
royendo Apr 6, 2026
00f06a4
hide newest/oldest based on table (if it needs it)
royendo Apr 7, 2026
31087c1
4 and
royendo Apr 7, 2026
67cc60e
remove sort logic from svelte since hidden
royendo Apr 7, 2026
78d5376
Update +page.svelte
royendo Apr 7, 2026
5114450
local code review
royendo Apr 7, 2026
34f65cc
as req; apply to logs and tables
royendo Apr 8, 2026
dbaf5b8
button
royendo Apr 8, 2026
8f294fa
Update ProjectTables.svelte
royendo Apr 8, 2026
074f20c
asreq and animated filter
royendo Apr 15, 2026
8a08f76
Merge branch 'main' into royendo/add-shared-table-toolbar
royendo Apr 15, 2026
ae6fbae
code qual
royendo Apr 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 27 additions & 67 deletions web-admin/src/features/projects/status/logs/ProjectLogsPage.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@
} from "@rilldata/web-common/runtime-client/sse-connection-manager";
import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import { V1LogLevel, type V1Log } from "@rilldata/web-common/runtime-client";
import Search from "@rilldata/web-common/components/search/Search.svelte";
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu";
import CaretDownIcon from "@rilldata/web-common/components/icons/CaretDownIcon.svelte";
import CaretUpIcon from "@rilldata/web-common/components/icons/CaretUpIcon.svelte";
import { TableToolbar } from "@rilldata/web-common/components/table-toolbar";
import type { FilterGroup } from "@rilldata/web-common/components/table-toolbar/types";
import {
createUrlFilterSync,
parseArrayParam,
Expand All @@ -34,7 +32,6 @@
let logs: LogEntry[] = [];
let logsContainer: HTMLDivElement;
let connectionError: string | null = null;
let filterDropdownOpen = false;
let searchText = parseStringParam($page.url.searchParams.get("q"));
let selectedLevels = parseArrayParam($page.url.searchParams.get("level"));
let mounted = false;
Expand Down Expand Up @@ -81,19 +78,19 @@
return matchesLevel && matchesSearch;
});

$: selectedLevelLabel = (() => {
if (selectedLevels.length === 0) return "All levels";
if (selectedLevels.length === 1) {
return (
filterableLevels.find((l) => l.value === selectedLevels[0])?.label ??
"1 level"
);
}
const first = filterableLevels.find(
(l) => l.value === selectedLevels[0],
)?.label;
return `${first}, +${selectedLevels.length - 1} other${selectedLevels.length > 2 ? "s" : ""}`;
})();
$: filterGroups = [
{
label: "Level",
key: "level",
options: filterableLevels.map((l) => ({
value: l.value,
label: l.label,
})),
selected: selectedLevels,
defaultValue: [],
multiSelect: true,
},
] satisfies FilterGroup[];

let unsubs: (() => void)[] = [];

Expand Down Expand Up @@ -244,55 +241,18 @@
</div>
</div>

<div class="flex flex-row items-center gap-x-4 min-h-9">
<div class="flex-1 min-w-0 min-h-9">
<Search
bind:value={searchText}
placeholder="Search"
large
autofocus={false}
showBorderOnFocus={false}
retainValueOnMount
/>
</div>

<DropdownMenu.Root bind:open={filterDropdownOpen}>
<DropdownMenu.Trigger
class="min-w-fit min-h-9 flex flex-row gap-1 items-center rounded-sm border bg-input {filterDropdownOpen
? 'bg-gray-200'
: 'hover:bg-surface-hover'} px-2 py-1"
>
<span class="text-fg-secondary font-medium">
{selectedLevelLabel}
</span>
{#if filterDropdownOpen}
<CaretUpIcon size="12px" />
{:else}
<CaretDownIcon size="12px" />
{/if}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="start" class="w-48">
{#each filterableLevels as level}
<DropdownMenu.CheckboxItem
closeOnSelect={false}
checked={selectedLevels.includes(level.value)}
onCheckedChange={() => toggleLevel(level.value)}
>
{level.label}
</DropdownMenu.CheckboxItem>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>

{#if selectedLevels.length > 0 || searchText}
<button
class="shrink-0 text-sm text-primary-500 hover:text-primary-600 whitespace-nowrap"
onclick={clearFilters}
>
Clear
</button>
{/if}
</div>
<TableToolbar
{searchText}
onSearchChange={(text) => {
searchText = text;
}}
{filterGroups}
onFilterChange={(key, value) => {
if (key === "level") toggleLevel(value);
}}
onClearAllFilters={clearFilters}
showSort={false}
/>

<div class="logs-container" bind:this={logsContainer}>
{#if hasConnectionError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@
import { useRuntimeClient } from "@rilldata/web-common/runtime-client/v2";
import { useQueryClient } from "@tanstack/svelte-query";
import Button from "@rilldata/web-common/components/button/Button.svelte";
import Search from "@rilldata/web-common/components/search/Search.svelte";
import * as DropdownMenu from "@rilldata/web-common/components/dropdown-menu";
import CaretDownIcon from "@rilldata/web-common/components/icons/CaretDownIcon.svelte";
import CaretUpIcon from "@rilldata/web-common/components/icons/CaretUpIcon.svelte";
import { TableToolbar } from "@rilldata/web-common/components/table-toolbar";
import type { FilterGroup } from "@rilldata/web-common/components/table-toolbar/types";
import {
ResourceKind,
prettyResourceKind,
Expand Down Expand Up @@ -43,8 +41,6 @@
filterSync.init($page.url);

let isConfirmDialogOpen = false;
let filterDropdownOpen = false;
let statusDropdownOpen = false;
let searchText = parseStringParam($page.url.searchParams.get("q"));
let selectedTypes = parseArrayParam($page.url.searchParams.get("kind"));
let selectedStatuses = parseArrayParam($page.url.searchParams.get("status"));
Expand Down Expand Up @@ -92,6 +88,31 @@
ResourceKind.Connector,
];

$: filterGroups = [
{
label: "Type",
key: "kind",
options: filterableTypes.map((t) => ({
value: t,
label: prettyResourceKind(t),
})),
selected: selectedTypes,
defaultValue: [],
multiSelect: true,
},
{
label: "Status",
key: "status",
options: statusFilters.map((s) => ({
value: s.value,
label: s.label,
})),
selected: selectedStatuses,
defaultValue: [],
multiSelect: true,
},
] satisfies FilterGroup[];

$: resources = useResources(runtimeClient);

// Parse errors
Expand Down Expand Up @@ -159,103 +180,19 @@
<section class="flex flex-col gap-y-4">
<h2 class="text-lg font-medium">Resources</h2>

<!-- Search, Filter, and Action Controls -->
<div class="flex flex-row items-center gap-x-4 min-h-9">
<div class="flex-1 min-w-0 min-h-9">
<Search
bind:value={searchText}
placeholder="Search"
large
autofocus={false}
showBorderOnFocus={false}
retainValueOnMount
/>
</div>

<DropdownMenu.Root bind:open={filterDropdownOpen}>
<DropdownMenu.Trigger
class="min-w-fit min-h-9 flex flex-row gap-1 items-center rounded-sm border bg-input {filterDropdownOpen
? 'bg-gray-200'
: 'hover:bg-surface-hover'} px-2 py-1"
>
<span class="text-fg-secondary font-medium">
{#if selectedTypes.length === 0}
All types
{:else if selectedTypes.length === 1}
{prettyResourceKind(selectedTypes[0])}
{:else}
{prettyResourceKind(selectedTypes[0])}, +{selectedTypes.length - 1} other{selectedTypes.length >
2
? "s"
: ""}
{/if}
</span>
{#if filterDropdownOpen}
<CaretUpIcon size="12px" />
{:else}
<CaretDownIcon size="12px" />
{/if}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="start" class="w-48">
{#each filterableTypes as type}
<DropdownMenu.CheckboxItem
closeOnSelect={false}
checked={selectedTypes.includes(type)}
onCheckedChange={() => toggleType(type)}
>
{prettyResourceKind(type)}
</DropdownMenu.CheckboxItem>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>

<DropdownMenu.Root bind:open={statusDropdownOpen}>
<DropdownMenu.Trigger
class="min-w-fit min-h-9 flex flex-row gap-1 items-center rounded-sm border bg-input {statusDropdownOpen
? 'bg-gray-200'
: 'hover:bg-surface-hover'} px-2 py-1"
>
<span class="text-fg-secondary font-medium">
{#if selectedStatuses.length === 0}
All statuses
{:else if selectedStatuses.length === 1}
{statusFilters.find((s) => s.value === selectedStatuses[0])
?.label ?? selectedStatuses[0]}
{:else}
{statusFilters.find((s) => s.value === selectedStatuses[0])?.label},
+{selectedStatuses.length - 1} other{selectedStatuses.length > 2
? "s"
: ""}
{/if}
</span>
{#if statusDropdownOpen}
<CaretUpIcon size="12px" />
{:else}
<CaretDownIcon size="12px" />
{/if}
</DropdownMenu.Trigger>
<DropdownMenu.Content align="start" class="w-48">
{#each statusFilters as status}
<DropdownMenu.CheckboxItem
closeOnSelect={false}
checked={selectedStatuses.includes(status.value)}
onCheckedChange={() => toggleStatus(status.value)}
>
{status.label}
</DropdownMenu.CheckboxItem>
{/each}
</DropdownMenu.Content>
</DropdownMenu.Root>

{#if selectedTypes.length > 0 || searchText || selectedStatuses.length > 0}
<button
class="shrink-0 text-sm text-primary-500 hover:text-primary-600 whitespace-nowrap"
onclick={clearFilters}
>
Clear
</button>
{/if}

<TableToolbar
{searchText}
onSearchChange={(text) => {
searchText = text;
}}
{filterGroups}
onFilterChange={(key, value) => {
if (key === "kind") toggleType(value);
if (key === "status") toggleStatus(value);
}}
onClearAllFilters={clearFilters}
showSort={false}
>
<Button
type="secondary"
large
Expand All @@ -268,7 +205,7 @@
<span class="hidden lg:inline">Refresh all sources and models</span>
<span class="lg:hidden">Refresh all</span>
</Button>
</div>
</TableToolbar>

{#if $resources.isLoading}
<DelayedSpinner isLoading={true} size="16px" />
Expand Down
Loading
Loading