prettier cards

This commit is contained in:
Diego Imbert
2026-02-05 17:19:31 +01:00
parent 0b8681cfce
commit bacdbbbf20
3 changed files with 198 additions and 188 deletions

View File

@@ -776,96 +776,110 @@
{/if}
</div>
<div class="grow min-h-0 mt-4">
<div class="grow min-h-0 bottom-splitpane-wrapper">
<Splitpanes>
<Pane minSize={40}>
<div class="flex flex-col h-full">
<!-- Runs table. Add overflow-hidden because scroll is handled inside the runs table based on this wrapper height -->
<div class="grow min-h-0 overflow-y-hidden overflow-x-auto">
{#if jobs}
<RunsTable
{jobs}
externalJobs={externalJobs ?? []}
omittedObscuredJobs={extendedJobs?.omitted_obscured_jobs ?? false}
showExternalJobs={graph !== 'RunChart'}
activeLabel={filters.label}
{selectionMode}
{lastFetchWentToEnd}
bind:selectedIds
bind:selectedWorkspace
on:loadExtra={loadExtra}
on:filterByPath={filterByPath}
on:filterByUser={filterByUser}
on:filterByFolder={filterByFolder}
on:filterByLabel={filterByLabel}
on:filterByConcurrencyKey={filterByConcurrencyKey}
on:filterByTag={filterByTag}
on:filterBySchedule={filterBySchedule}
on:filterByWorker={filterByWorker}
bind:this={runsTable}
perPage={filters.per_page}
></RunsTable>
{:else}
<div class="gap-1 flex flex-col">
{#each new Array(8) as _}
<Skeleton layout={[[3]]} />
{/each}
</div>
{/if}
</div>
<div class="bg-surface border-t flex text-xs px-2 py-1 items-center gap-4">
<div class="flex-1"></div>
<Toggle
size="xs"
bind:checked={autoRefresh}
on:change={() => {
localStorage.setItem('auto_refresh_in_runs', autoRefresh ? 'true' : 'false')
}}
options={{ right: 'Auto-refresh' }}
textClass="whitespace-nowrap"
/>
<Select
class="w-24"
bind:value={
() => filters.per_page,
(newPerPage) => {
filters.per_page = newPerPage
if (newPerPage > (jobs?.length ?? 1000)) loadExtra()
<div class="h-full flex">
<div class="flex flex-col flex-1 m-4">
<!-- Runs table. Add overflow-hidden because scroll is handled inside the runs table based on this wrapper height -->
<div class="grow min-h-0 overflow-y-hidden overflow-x-auto">
{#if jobs}
<RunsTable
{jobs}
externalJobs={externalJobs ?? []}
omittedObscuredJobs={extendedJobs?.omitted_obscured_jobs ?? false}
showExternalJobs={graph !== 'RunChart'}
activeLabel={filters.label}
{selectionMode}
{lastFetchWentToEnd}
bind:selectedIds
bind:selectedWorkspace
on:loadExtra={loadExtra}
on:filterByPath={filterByPath}
on:filterByUser={filterByUser}
on:filterByFolder={filterByFolder}
on:filterByLabel={filterByLabel}
on:filterByConcurrencyKey={filterByConcurrencyKey}
on:filterByTag={filterByTag}
on:filterBySchedule={filterBySchedule}
on:filterByWorker={filterByWorker}
bind:this={runsTable}
perPage={filters.per_page}
></RunsTable>
{:else}
<div class="gap-1 flex flex-col">
{#each new Array(8) as _}
<Skeleton layout={[[3]]} />
{/each}
</div>
{/if}
</div>
<div
class="bg-surface-tertiary border rounded-b-md flex text-xs px-2 py-1 items-center gap-4"
>
<div class="flex-1"></div>
<Toggle
size="xs"
bind:checked={autoRefresh}
on:change={() => {
localStorage.setItem('auto_refresh_in_runs', autoRefresh ? 'true' : 'false')
}}
options={{ right: 'Auto-refresh' }}
textClass="whitespace-nowrap"
/>
<Select
class="w-24"
bind:value={
() => filters.per_page,
(newPerPage) => {
filters.per_page = newPerPage
if (newPerPage > (jobs?.length ?? 1000)) loadExtra()
}
}
}
onCreateItem={(v) => (filters.per_page = parseInt(v))}
items={[
{ value: 25, label: '25' },
{ value: 100, label: '100' },
{ value: 1000, label: '1000' },
{ value: 10000, label: '10000' }
]}
transformInputSelectedText={(_, v) => `${v} / page`}
/>
onCreateItem={(v) => (filters.per_page = parseInt(v))}
items={[
{ value: 25, label: '25' },
{ value: 100, label: '100' },
{ value: 1000, label: '1000' },
{ value: 10000, label: '10000' }
]}
transformInputSelectedText={(_, v) => `${v} / page`}
/>
</div>
</div>
</div>
</Pane>
<AnimatedPane size={40} minSize={15} class="flex flex-col" opened={selectedIds.length > 0}>
{#if selectionMode === 're-run'}
<BatchReRunOptionsPane {selectedIds} bind:options={batchReRunOptions} />
{:else if selectedIds.length === 1}
{#if selectedIds[0] === '-'}
<div class="p-4">There is no information available for this job</div>
{:else}
<JobRunsPreview
id={selectedIds[0]}
workspace={selectedWorkspace}
on:filterByConcurrencyKey={filterByConcurrencyKey}
on:filterByWorker={filterByWorker}
/>
<div class="mt-14 overflow-y-auto pr-4">
{#if selectionMode === 're-run'}
<BatchReRunOptionsPane {selectedIds} bind:options={batchReRunOptions} />
{:else if selectedIds.length === 1}
{#if selectedIds[0] === '-'}
<div class="p-4">There is no information available for this job</div>
{:else}
<JobRunsPreview
id={selectedIds[0]}
workspace={selectedWorkspace}
on:filterByConcurrencyKey={filterByConcurrencyKey}
on:filterByWorker={filterByWorker}
/>
{/if}
{:else if selectedIds.length > 1}
<div class="text-xs m-4"
>There are {selectedIds.length} jobs selected. Choose 1 to see detailed information</div
>
{/if}
{:else if selectedIds.length > 1}
<div class="text-xs m-4"
>There are {selectedIds.length} jobs selected. Choose 1 to see detailed information</div
>
{/if}
</div>
</AnimatedPane>
</Splitpanes>
</div>
</div>
{/if}
<style>
:global(.bottom-splitpane-wrapper .splitpanes__splitter) {
background-color: transparent !important;
border: none !important;
opacity: 0 !important;
}
</style>

View File

@@ -109,8 +109,8 @@
bind:this={jobLoader}
/>
<div class="h-full overflow-y-auto">
<div class="flex flex-col items-start p-4 pb-8 min-h-full">
<div class="h-full">
<div class="flex flex-col items-start pb-4 min-h-full">
{#if job}
{@const isFlow = job?.job_kind == 'flow' || isFlowPreview(job?.job_kind)}
<JobDetailHeader

View File

@@ -2,7 +2,7 @@
import type { Job } from '$lib/gen'
import RunRow from './RunRow.svelte'
import VirtualList from '@tutorlatin/svelte-tiny-virtual-list'
import { createEventDispatcher, onMount } from 'svelte'
import { createEventDispatcher } from 'svelte'
import Tooltip from '../Tooltip.svelte'
import { AlertTriangle } from 'lucide-svelte'
import Popover from '../Popover.svelte'
@@ -116,7 +116,6 @@
}
let tableHeight: number = $state(0)
let headerHeight: number = $state(0)
let containerWidth: number = $state(0)
// const MAX_ITEMS = perPage
@@ -146,12 +145,6 @@
return `${jc}${isTruncated ? '+' : ''} job${jc != 1 ? 's' : ''}`
}
function computeHeight() {
tableHeight = document.querySelector('#runs-table-wrapper')!.parentElement?.clientHeight ?? 0
}
onMount(() => {
computeHeight()
})
const dispatch = createEventDispatcher()
let scrollToIndex = $state(0)
@@ -196,14 +189,12 @@
const showTag = $derived(containerWidth > 700)
</script>
<svelte:window onresize={() => computeHeight()} />
<div
class="divide-y h-full min-w-[650px] [&>.virtual-list-wrapper::-webkit-scrollbar-track]:bg-surface-tertiary"
class="divide-y h-full flex flex-col min-w-[650px]"
id="runs-table-wrapper"
bind:clientWidth={containerWidth}
>
<div bind:clientHeight={headerHeight}>
<div>
<div
class="grid sticky top-0 w-full min-h-6 my-2 pr-4 items-center"
class:grid-runs-table={!containsLabel && !selectionMode && showTag}
@@ -255,105 +246,110 @@
<div class=""></div>
</div>
</div>
{#if jobs?.length == 0 && (!showExternalJobs || externalJobs?.length == 0)}
<div class="text-xs text-secondary p-8"> No jobs found for the selected filters. </div>
{:else}
<VirtualList
width="100%"
height={tableHeight - headerHeight}
itemCount={flatJobs?.length ?? 3}
itemSize={42}
overscanCount={20}
{stickyIndices}
{scrollToIndex}
scrollToAlignment="center"
>
{#snippet header()}{/snippet}
{#snippet item({ index, style })}
<div {style} class="w-full bg-surface-tertiary">
{#if flatJobs}
{@const jobOrDate = flatJobs[index]}
<div
bind:clientHeight={tableHeight}
class="relative flex-1 border rounded-t-md overflow-clip [&>.virtual-list-wrapper::-webkit-scrollbar-track]:bg-surface-tertiary"
>
{#if jobs?.length == 0 && (!showExternalJobs || externalJobs?.length == 0)}
<div class="text-xs text-secondary p-8"> No jobs found for the selected filters. </div>
{:else}
<VirtualList
width="100%"
height={tableHeight}
itemCount={flatJobs?.length ?? 3}
itemSize={42}
overscanCount={20}
{stickyIndices}
{scrollToIndex}
scrollToAlignment="center"
>
{#snippet header()}{/snippet}
{#snippet item({ index, style })}
<div {style} class="w-full bg-surface-tertiary">
{#if flatJobs}
{@const jobOrDate = flatJobs[index]}
{#if jobOrDate}
{#if jobOrDate?.type === 'date'}
<div
class="border-b bg-surface-tertiary py-2 font-semibold text-xs pl-4 h-[42px] flex items-end"
>
{jobOrDate.date}
</div>
{#if jobOrDate}
{#if jobOrDate?.type === 'date'}
<div
class="border-b bg-surface-tertiary py-2 font-semibold text-xs pl-4 h-[42px] flex items-end"
>
{jobOrDate.date}
</div>
{:else}
<div class="flex flex-row items-center h-full w-full">
<RunRow
{containsLabel}
{showTag}
job={jobOrDate.job}
selected={jobOrDate.job.id !== '-' && selectedIds.includes(jobOrDate.job.id)}
{selectionMode}
on:select={() => {
const jobId = jobOrDate.job.id
if (selectionMode) {
if (selectedIds.includes(jobOrDate.job.id)) {
selectedIds = selectedIds.filter((id) => id != jobId)
} else {
selectedIds.push(jobId)
selectedIds = selectedIds
}
} else {
if (
JSON.stringify(selectedIds) !== JSON.stringify([jobOrDate.job.id]) ||
selectedWorkspace !== jobOrDate.job.workspace_id
) {
selectedWorkspace = jobOrDate.job.workspace_id
selectedIds = [jobOrDate.job.id]
dispatch('select')
} else {
selectedIds = []
selectedWorkspace = undefined
dispatch('select')
}
}
}}
{activeLabel}
on:filterByLabel
on:filterByPath
on:filterByUser
on:filterByFolder
on:filterByConcurrencyKey
on:filterBySchedule
on:filterByWorker
{containerWidth}
/>
</div>
{/if}
{:else}
<div class="flex flex-row items-center h-full w-full">
<RunRow
{containsLabel}
{showTag}
job={jobOrDate.job}
selected={jobOrDate.job.id !== '-' && selectedIds.includes(jobOrDate.job.id)}
{selectionMode}
on:select={() => {
const jobId = jobOrDate.job.id
if (selectionMode) {
if (selectedIds.includes(jobOrDate.job.id)) {
selectedIds = selectedIds.filter((id) => id != jobId)
} else {
selectedIds.push(jobId)
selectedIds = selectedIds
}
} else {
if (
JSON.stringify(selectedIds) !== JSON.stringify([jobOrDate.job.id]) ||
selectedWorkspace !== jobOrDate.job.workspace_id
) {
selectedWorkspace = jobOrDate.job.workspace_id
selectedIds = [jobOrDate.job.id]
dispatch('select')
} else {
selectedIds = []
selectedWorkspace = undefined
dispatch('select')
}
}
}}
{activeLabel}
on:filterByLabel
on:filterByPath
on:filterByUser
on:filterByFolder
on:filterByConcurrencyKey
on:filterBySchedule
on:filterByWorker
{containerWidth}
/>
</div>
{JSON.stringify(jobOrDate)}
{/if}
{:else}
{JSON.stringify(jobOrDate)}
<div class="flex flex-row items-center h-full w-full">
<div class="w-1/12 text-2xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-3/12 text-xs">...</div>
</div>
{/if}
{:else}
<div class="flex flex-row items-center h-full w-full">
<div class="w-1/12 text-2xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-4/12 text-xs">...</div>
<div class="w-3/12 text-xs">...</div>
</div>
{/if}
</div>
{/snippet}
{#snippet footer()}
<div
>{#if !lastFetchWentToEnd && jobs && jobs.length >= perPage}
<button
class="text-xs text-accent text-center w-full pb-2"
onclick={() => {
dispatch('loadExtra')
}}
>
Load next {perPage} jobs
</button>
{/if}</div
>
{/snippet}
</VirtualList>
{/if}
</div>
{/snippet}
{#snippet footer()}
<div
>{#if !lastFetchWentToEnd && jobs && jobs.length >= perPage}
<button
class="text-xs text-accent text-center w-full pb-2"
onclick={() => {
dispatch('loadExtra')
}}
>
Load next {perPage} jobs
</button>
{/if}</div
>
{/snippet}
</VirtualList>
{/if}
</div>
</div>
<style>