Files
windmill/frontend/src/lib/components/instanceSettings/IndexerMemorySettings.svelte
Ruben Fiszel e3620e074e fix: serve index disk storage sizes from /srch/ endpoint (#8511)
* [ee] fix: serve index disk storage sizes from /srch/ endpoint

On multi-container deployments, the API server doesn't have the index
files on its local disk, so disk size was always reported as 0.0B.

Added a new GET /srch/index/storage/disk endpoint that calculates disk
sizes on the indexer process (which owns the files). The frontend now
fetches disk sizes from this endpoint in parallel with the status call.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update ee-repo-ref to 71aab648925f31cde37efd31d79a7f3a977fd42a

This commit updates the EE repository reference after PR #480 was merged in windmill-ee-private.

Previous ee-repo-ref: b3e0000e2528809302c18f36930aebf3d004747a

New ee-repo-ref: 71aab648925f31cde37efd31d79a7f3a977fd42a

Automated by sync-ee-ref workflow.

* chore: update ee-repo-ref to indexer-disk-storage-zero branch

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* chore: update sqlx metadata and ee-repo-ref

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: windmill-internal-app[bot] <windmill-internal-app[bot]@users.noreply.github.com>
2026-03-25 07:56:45 +00:00

265 lines
8.2 KiB
Svelte

<script lang="ts">
import { Button } from '$lib/components/common'
import ConfirmationModal from '../common/confirmationModal/ConfirmationModal.svelte'
import { IndexSearchService } from '$lib/gen'
import type { GetIndexerStatusResponse, GetIndexDiskStorageSizesResponse } from '$lib/gen'
import { sendUserToast } from '$lib/toast'
import { displaySize } from '$lib/utils'
import Tooltip from '../Tooltip.svelte'
import IntegerInput from '../IntegerInput.svelte'
import InputError from '../InputError.svelte'
import Label from '../Label.svelte'
import { Loader2, RefreshCw } from 'lucide-svelte'
import type { Writable } from 'svelte/store'
interface Props {
values: Writable<Record<string, any>>
disabled?: boolean
errors?: Record<string, string>
}
let { values, disabled = false, errors = {} }: Props = $props()
let clearJobsIndexModalOpen = $state(false)
let clearServiceLogsIndexModalOpen = $state(false)
let status: GetIndexerStatusResponse | undefined = $state(undefined)
let diskSizes: GetIndexDiskStorageSizesResponse | undefined = $state(undefined)
let statusLoading = $state(true)
let statusError = $state(false)
function formatTimeAgo(isoDate: string): string {
const diffMs = Date.now() - new Date(isoDate).getTime()
const diffSecs = Math.floor(diffMs / 1000)
if (diffSecs < 60) return `${diffSecs}s ago`
const diffMins = Math.floor(diffSecs / 60)
if (diffMins < 60) return `${diffMins}m ago`
const diffHours = Math.floor(diffMins / 60)
return `${diffHours}h ago`
}
async function loadStatus() {
statusLoading = true
statusError = false
try {
const [statusRes, diskRes] = await Promise.all([
IndexSearchService.getIndexerStatus(),
IndexSearchService.getIndexDiskStorageSizes().catch(() => undefined)
])
status = statusRes
diskSizes = diskRes
} catch (e) {
status = undefined
diskSizes = undefined
statusError = true
} finally {
statusLoading = false
}
}
loadStatus()
</script>
<div class="space-y-6">
<div class="flex flex-col gap-1">
<label for="writer_memory_budget" class="block text-xs font-semibold text-emphasis">
Index writer memory budget (MB)
<Tooltip>
The allocated memory arena for the indexer. A bigger value means less writing to disk and
potentially higher indexing throughput
</Tooltip>
</label>
<IntegerInput
placeholder="300"
id="writer_memory_budget"
{disabled}
error={errors.writer_memory_budget ?? ''}
value={$values['indexer_settings'].writer_memory_budget != null
? $values['indexer_settings'].writer_memory_budget / (1024 * 1024)
: undefined}
oninput={(v) => {
if (v == null) {
const { writer_memory_budget: _, ...rest } = $values['indexer_settings']
$values['indexer_settings'] = rest
} else {
$values['indexer_settings'] = {
...$values['indexer_settings'],
writer_memory_budget: v * (1024 * 1024)
}
}
}}
/>
<InputError error={errors.writer_memory_budget ?? ''} />
</div>
<div class="flex flex-col gap-1">
<label for="max_index_time_window_secs" class="block text-xs font-semibold text-emphasis">
Index time window (days)
<Tooltip>
Maximum age of items to include in the index. Jobs and logs older than this window will not
be indexed and will be cleaned up from the index. Set to 0 to disable (index everything
within the retention period).
</Tooltip>
</label>
<IntegerInput
placeholder="7"
id="max_index_time_window_secs"
{disabled}
error={errors.max_index_time_window_secs ?? ''}
value={$values['indexer_settings'].max_index_time_window_secs != null
? Math.round($values['indexer_settings'].max_index_time_window_secs / 86400)
: undefined}
oninput={(v) => {
if (v == null) {
const { max_index_time_window_secs: _, ...rest } = $values['indexer_settings']
$values['indexer_settings'] = rest
} else {
$values['indexer_settings'] = {
...$values['indexer_settings'],
max_index_time_window_secs: v * 86400
}
}
}}
/>
<InputError error={errors.max_index_time_window_secs ?? ''} />
</div>
<Label label="Indexer status">
{#snippet action()}
<button
onclick={loadStatus}
disabled={statusLoading}
class="inline-flex items-center text-secondary hover:text-primary disabled:opacity-50"
>
{#if statusLoading}
<Loader2 size={14} class="animate-spin" />
{:else}
<RefreshCw size={14} />
{/if}
</button>
{/snippet}
{#if status}
<div class="flex flex-col gap-2">
{#each [{ label: 'Job indexer', entry: status.job_indexer }, { label: 'Service log indexer', entry: status.log_indexer }] as { label, entry } (label)}
<div class="flex flex-row items-center gap-2 text-xs">
<span
class="inline-block w-2 h-2 rounded-full {entry?.is_alive
? 'bg-green-500'
: 'bg-red-500'}"
></span>
<span class="text-primary font-medium">{label}:</span>
<span
class="font-semibold {entry?.is_alive
? 'text-green-600 dark:text-green-400'
: 'text-red-600 dark:text-red-400'}"
>
{entry?.is_alive ? 'Running' : 'Stopped'}
</span>
{#if entry?.last_locked_at}
<span class="text-tertiary text-2xs">
Last active: {formatTimeAgo(entry.last_locked_at)}
</span>
{/if}
</div>
{/each}
</div>
{:else if statusError && !statusLoading}
<span class="text-2xs text-secondary">
Could not fetch indexer status. Search may not be enabled in this build.
</span>
{/if}
</Label>
<Label label="Index storage">
{#if status}
<div class="flex flex-col gap-1 text-2xs text-tertiary">
<span>
Jobs index:
{#if diskSizes?.job_index_disk_size_bytes != null}
Disk: {displaySize(diskSizes.job_index_disk_size_bytes) ?? 'N/A'}
{/if}
{#if status.job_indexer?.storage?.s3_size_bytes != null}
{#if diskSizes?.job_index_disk_size_bytes != null}&middot;{/if}
S3: {displaySize(status.job_indexer.storage.s3_size_bytes) ?? 'N/A'}
{/if}
</span>
<span>
Service logs index:
{#if diskSizes?.log_index_disk_size_bytes != null}
Disk: {displaySize(diskSizes.log_index_disk_size_bytes) ?? 'N/A'}
{/if}
{#if status.log_indexer?.storage?.s3_size_bytes != null}
{#if diskSizes?.log_index_disk_size_bytes != null}&middot;{/if}
S3: {displaySize(status.log_indexer.storage.s3_size_bytes) ?? 'N/A'}
{/if}
</span>
</div>
{/if}
</Label>
<Label label="Clear index">
<span class="text-xs text-secondary"
>These buttons will clear the whole index, and the service will start reindexing from scratch.
Full text search might be down during this time.</span
>
<div class="flex flex-col gap-3">
<div class="flex flex-row items-center gap-2">
<Button
variant="default"
unifiedSize="sm"
onclick={() => {
clearJobsIndexModalOpen = true
}}
>
Clear jobs index
</Button>
</div>
<div class="flex flex-row items-center gap-2">
<Button
variant="default"
unifiedSize="sm"
onclick={() => {
clearServiceLogsIndexModalOpen = true
}}
>
Clear service logs index
</Button>
</div>
</div>
</Label>
<ConfirmationModal
title="Clear jobs index"
confirmationText="Clear"
open={clearJobsIndexModalOpen}
type="danger"
on:canceled={() => {
clearJobsIndexModalOpen = false
}}
on:confirmed={async () => {
const r = await IndexSearchService.clearIndex({
idxName: 'JobIndex'
})
sendUserToast(r)
clearJobsIndexModalOpen = false
}}
>
Are you sure you want to clear the jobs index? The service will start reindexing from scratch.
Full text search might be down during this time.
</ConfirmationModal>
<ConfirmationModal
title="Clear service logs index"
confirmationText="Clear"
open={clearServiceLogsIndexModalOpen}
type="danger"
on:canceled={() => {
clearServiceLogsIndexModalOpen = false
}}
on:confirmed={async () => {
const r = await IndexSearchService.clearIndex({
idxName: 'ServiceLogIndex'
})
sendUserToast(r)
clearServiceLogsIndexModalOpen = false
}}
>
Are you sure you want to clear the service logs index? The service will start reindexing from
scratch. Full text search might be down during this time.
</ConfirmationModal>
</div>