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}