diff --git a/frontend/src/lib/components/DateTimeInput.svelte b/frontend/src/lib/components/DateTimeInput.svelte index d919c97100..ba3a848ff9 100644 --- a/frontend/src/lib/components/DateTimeInput.svelte +++ b/frontend/src/lib/components/DateTimeInput.svelte @@ -184,13 +184,13 @@ unifiedSize="md" wrapperClasses="h-full" {disabled} + iconOnly + endIcon={{ icon: X }} on:click={() => { value = null dispatch('clear') }} - > - - + > {/if} -
- - {#if filters.min_ts || filters.max_ts} - - {/if} - (filters.min_ts = null)} - label="From" - class={filters.min_ts || filters.max_ts - ? '' - : 'relative top-0 bottom-0 left-0 right-0 h-[34px]'} - /> - - - - {#if filters.max_ts || filters.min_ts} - - {/if} - (filters.max_ts = null)} - bind:date={filters.max_ts} - label="To" - class={filters.min_ts || filters.max_ts - ? '' - : 'relative top-0 bottom-0 left-0 right-0 h-[34px]'} - /> - - - {#if filters.min_ts || filters.max_ts} - - - - {/if} +
+ jobsLoader?.loadJobs(true)} + loading={jobsLoader?.loading} + items={runsTimeframes} + bind:value={_timeframe.timeframe} + />
@@ -650,17 +614,11 @@ bind:argFilter={filters.arg} bind:resultFilter={filters.result} on:change={reloadJobsWithoutFilterError} - on:successChange={(e) => { - if (e.detail == 'running' && filters.max_ts != undefined) { - filters.max_ts = null - } - }} {usernames} {folders} {paths} mobile={innerWidth < verySmallScreenWidth} small={innerWidth < smallScreenWidth} - calendarSmall={!filters.min_ts && !filters.max_ts} />
@@ -696,16 +654,19 @@ {/if} {#if graph === 'RunChart'} + {@const manualTimeframe = timeframe.type === 'manual' ? timeframe : undefined} { - filters.min_ts = zoom.min.toISOString() - filters.max_ts = zoom.max.toISOString() + _timeframe.timeframe = buildManualTimeframe( + zoom.min.toISOString(), + zoom.max.toISOString() + ) jobsLoader?.loadJobs(true) }} onPointClicked={(ids) => { @@ -714,13 +675,15 @@ /> {:else if graph === 'ConcurrencyChart'} { - filters.min_ts = zoom.min.toISOString() - filters.max_ts = zoom.max.toISOString() + _timeframe.timeframe = buildManualTimeframe( + zoom.min.toISOString(), + zoom.max.toISOString() + ) jobsLoader?.loadJobs(true) }} /> diff --git a/frontend/src/lib/components/ServiceLogsInner.svelte b/frontend/src/lib/components/ServiceLogsInner.svelte index cdf2ed5c0f..0f4f923cd6 100644 --- a/frontend/src/lib/components/ServiceLogsInner.svelte +++ b/frontend/src/lib/components/ServiceLogsInner.svelte @@ -4,12 +4,15 @@ const bubble = createBubbler() import { IndexSearchService, ServiceLogsService } from '$lib/gen' - import ManuelDatePicker from './runs/ManuelDatePicker.svelte' + import TimeframeSelect, { + serviceLogsTimeframes, + useUrlSyncedTimeframe + } from './runs/TimeframeSelect.svelte' import CalendarPicker from './common/calendarPicker/CalendarPicker.svelte' import LogViewer from './LogViewer.svelte' import Toggle from './Toggle.svelte' import { sendUserToast } from '$lib/toast' - import { onDestroy, tick, untrack } from 'svelte' + import { onDestroy, tick } from 'svelte' import { Loader2 } from 'lucide-svelte' import { copyToClipboard, scroll_into_view_if_needed_polyfill, truncateRev } from '$lib/utils' import LogSnippetViewer from './LogSnippetViewer.svelte' @@ -20,6 +23,7 @@ import Select from './select/Select.svelte' import { goto } from '$lib/navigation' import { page } from '$app/stores' + import { watch } from 'runed' interface Props { searchTerm: string @@ -32,9 +36,6 @@ let minTs: undefined | string = $state(undefined) let maxTs: undefined | string = $state(undefined) - let minTsManual: undefined | string = $state($page.url.searchParams.get('minTs') ?? undefined) - let maxTsManual: undefined | string = $state($page.url.searchParams.get('maxTs') ?? undefined) - let max_lines: undefined | number = $state(undefined) // let lastSeen: undefined | string = undefined @@ -58,15 +59,17 @@ let timeout: number | undefined = $state(undefined) let allLogs: ByMode | undefined = $state(undefined) - let manualPicker: ManuelDatePicker | undefined = $state(undefined) + + let _timeframe = useUrlSyncedTimeframe(serviceLogsTimeframes) + let timeframe = $derived(_timeframe.timeframe) + + let [minTsManual, maxTsManual] = $derived( + timeframe.type === 'manual' ? [timeframe.minTs ?? undefined, timeframe.maxTs ?? undefined] : [] + ) let upTo: undefined | string = $state(undefined) let upToIsLatest = $state(true) - function onManualChanges() { - getAllLogs(minTsManual ?? maxTs, maxTsManual) - } - function getAllLogs(queryMinTs: string | undefined, queryMaxTs: string | undefined) { timeout && clearTimeout(timeout) loading = true @@ -151,11 +154,6 @@ if (autoRefresh && searchTerm === '' && !maxTsManual) { timeout = setTimeout(() => { if (searchTerm !== '') return - let minMax = manualPicker?.computeMinMax() - if (minMax) { - maxTsManual = minMax?.maxTs ?? undefined - minTsManual = minMax?.minTs ?? undefined - } let maxTsPlus1 = maxTs ? new Date(new Date(maxTs).getTime() + 1000) : undefined getAllLogs(maxTsPlus1?.toISOString(), undefined) }, 5000) @@ -315,8 +313,6 @@ ) { const params = new URLSearchParams() if (searchTerm) params.set('searchTerm', searchTerm) - if (minTs) params.set('minTs', minTs) - if (maxTs) params.set('maxTs', maxTs) if (selected?.mode) params.set('mode', selected.mode) if (selected?.workerGroup) params.set('workerGroup', selected.workerGroup) if (selected?.hostname) params.set('hostname', selected.hostname) @@ -435,13 +431,22 @@ ) } - $effect(() => { - minTsManual || maxTsManual || untrack(() => onManualChanges()) - }) - $effect(() => { - ;[searchTerm, selected, minTsManual, maxTsManual, allLogs] - untrack(() => searchLogs(searchTerm, selected, minTsManual, maxTsManual, allLogs)) - }) + watch( + () => timeframe, + () => { + const ts = timeframe.computeMinMax() + minTs = undefined + maxTs = undefined + allLogs = undefined + getAllLogs(ts.minTs ?? undefined, ts.maxTs ?? undefined) + } + ) + watch( + () => [searchTerm, selected, timeframe, allLogs], + () => { + searchLogs(searchTerm, selected, minTsManual, maxTsManual, allLogs) + } + ) @@ -477,71 +482,19 @@ class="flex flex-col lg:flex-row gap-y-1 justify-between w-full relative pb-4 gap-x-0.5" id="service-logs-date-pickers" > -
- - { - minTs = undefined - maxTs = undefined - allLogs = undefined - minTsManual = detail - getAllLogs(minTsManual, maxTsManual) - }} - placement="top-start" - /> -
- minTsManual ?? null, (v) => (minTsManual = v ?? undefined)} - bind:maxTs={() => maxTsManual ?? null, (v) => (maxTsManual = v ?? undefined)} - bind:this={manualPicker} + { + wrapperClasses="w-full" + onClick={() => { minTs = undefined maxTs = undefined allLogs = undefined - getAllLogs(minTsManual, maxTsManual) + const ts = timeframe.computeMinMax() + getAllLogs(ts.minTs ?? undefined, ts.maxTs ?? undefined) }} - serviceLogsChoices - loadText={searchTerm === '' ? 'Last 1000 logfiles' : 'All time'} /> -
- - { - minTs = undefined - maxTs = undefined - allLogs = undefined - maxTsManual = detail - getAllLogs(minTsManual, maxTsManual) - }} - /> -
- import { RefreshCw } from 'lucide-svelte' - import { Button } from '../common' - import { createEventDispatcher } from 'svelte' - - interface Props { - minTs: string | null - maxTs: string | null - loading?: boolean - selectedManualDate?: number - loadText?: string | undefined - serviceLogsChoices?: boolean - numberOfLastJobsToFetch?: number - } - - let { - minTs = $bindable(), - maxTs = $bindable(), - loading = false, - selectedManualDate = $bindable(0), - loadText = undefined, - serviceLogsChoices = false, - numberOfLastJobsToFetch = 1000 - }: Props = $props() - - export function computeMinMax(): { minTs: string; maxTs: string | null } | undefined { - return manualDates[selectedManualDate].computeMinMax() - } - - export function resetChoice() { - selectedManualDate = 0 - } - - function computeMinMaxInc(inc: number) { - let minTs = new Date(new Date().getTime() - inc).toISOString() - let maxTs = null - return { minTs, maxTs } - } - - const fixedManualDates: { - label: string - computeMinMax: () => { minTs: string; maxTs: string | null } | undefined - }[] = [ - ...(!serviceLogsChoices - ? [ - { - label: 'Within 30 seconds', - computeMinMax: () => computeMinMaxInc(30 * 1000) - }, - { - label: 'Within last minute', - computeMinMax: () => computeMinMaxInc(60 * 1000) - } - ] - : []), - { - label: 'Within last 5 minutes', - computeMinMax: () => computeMinMaxInc(5 * 60 * 1000) - }, - { - label: 'Within last 30 minutes', - computeMinMax: () => computeMinMaxInc(30 * 60 * 1000) - }, - { - label: 'Within last 24 hours', - computeMinMax: () => computeMinMaxInc(24 * 60 * 60 * 1000) - }, - { - label: 'Within last 7 days', - computeMinMax: () => computeMinMaxInc(7 * 24 * 60 * 60 * 1000) - }, - { - label: 'Within last month', - computeMinMax: () => computeMinMaxInc(30 * 24 * 60 * 60 * 1000) - } - ] - - let manualDates = $derived([ - { - label: loadText ?? `Last ${numberOfLastJobsToFetch} runs`, - computeMinMax: () => { - return undefined - } - }, - ...fixedManualDates - ]) - - const dispatch = createEventDispatcher() - - - diff --git a/frontend/src/lib/components/runs/RunsFilter.svelte b/frontend/src/lib/components/runs/RunsFilter.svelte index df55e1c383..48a402afaa 100644 --- a/frontend/src/lib/components/runs/RunsFilter.svelte +++ b/frontend/src/lib/components/runs/RunsFilter.svelte @@ -15,8 +15,6 @@ .default(null), show_skipped: z.boolean().default(false), show_schedules: z.boolean().default(true), - min_ts: z.string().nullable().default(null), - max_ts: z.string().nullable().default(null), schedule_path: z.string().nullable().default(null), job_kinds: z.string().default('runs'), all_workspaces: z.boolean().default(false), @@ -90,7 +88,6 @@ | 'tag' | 'schedulePath' small?: boolean - calendarSmall?: boolean } let { @@ -116,9 +113,8 @@ usernames = [], folders = [], allWorkspaces = $bindable(), - filterBy = $bindable(), - small = false, - calendarSmall = false + filterBy = $bindable('path'), + small = false }: Props = $props() const dispatch = createEventDispatcher() @@ -526,7 +522,7 @@ - {#if small && !calendarSmall} + {#if small}