{t('admin.jobs.title', 'Jobs')}

{t( 'admin.jobs.hint', 'Fires periodic + on-demand jobs. Consistency checks are safe to run at any time — they are read-only.' )}

{#if hasBatch} {/if}
{#if loadError}

{loadError}

{:else if !jobs}

{t('common.loading', 'Loading…')}

{:else if jobs.length === 0}

{t('admin.jobs.none_registered', 'No jobs registered.')}

{:else} {#each jobs as job (job.name)} {@const runs = runsByJob[job.name]} {@const runsErr = runsErrorByJob[job.name]} {@const runsLoading = runsLoadingByJob[job.name]} {@const expandedRun = expandedRunByJob[job.name] ?? null} {@const canExpand = isRecoverable(job)} {#if expandedJob === job.name} {/if} {/each}
{t('admin.jobs.col_name', 'Name')} {t('admin.jobs.col_cadence', 'Cadence')} {t('admin.jobs.col_last_run', 'Last run')} {t('admin.jobs.col_outcome', 'Outcome')} {t('admin.jobs.col_state', 'State')} {t('admin.jobs.col_actions', 'Actions')}
{#if canExpand} {:else} {job.name} {/if} {cadenceLabel(job)} {timeAgo(job.last_run_at)}
{outcomeLabel(job)} {#if actionableFindingCount(job) > 0} {@const findings = actionableFindingCount(job)} {t('admin.jobs.n_findings', { n: findings }, '{{n}} findings')} {/if} {#if anomalyFindingCount(job) > 0} {@const notices = anomalyFindingCount(job)} {t('admin.jobs.n_notices', { n: notices }, '{{n}} notices')} {/if}
{#if isRunning(job)} {t('admin.jobs.state_running', 'running')} {:else} — {/if} {#if supportsDeep(job.name)} {/if} {#if isRunning(job) && canExpand} {/if}

{t('admin.jobs.runs_title', 'Recent runs')}

{#if runsErr}

{runsErr}

{:else if !runs}

{t('common.loading', 'Loading…')}

{:else if runs.length === 0}

{t('admin.jobs.no_runs', 'No runs yet.')}

{:else} {#each runs as run (run.id)} {@const scanned = statNumber(run, 'scanned_count')} {@const findingCount = statNumber(run, 'finding_count')} {@const isRunExpanded = expandedRun === run.id} {#if isRunExpanded} {@const findings = findingsByRun[run.id]} {@const findingsErr = findingsErrorByRun[run.id]} {@const fLoading = findingsLoadingByRun[run.id]} {/if} {/each}
{t('admin.jobs.col_started_at', 'Started')} {t('admin.jobs.col_status', 'Status')} {t('admin.jobs.col_duration', 'Duration')} {t('admin.jobs.col_progress', 'Progress')} {t('admin.jobs.col_findings', 'Findings')} {t('admin.jobs.col_error', 'Error')}
{timeAgo(run.started_at)} {run.status} {runDurationLabel(run)} {#if run.progress} {@const barPct = Math.min( 100, Math.max(0, run.progress.fraction * 100) )} {@const pctLabel = (run.progress.fraction * 100).toFixed(1) + '%'}
{run.progress.scanned}/{run.progress.total}
{:else if scanned != null} {t( 'admin.jobs.progress_scanned_only', { n: scanned }, '{{n}} scanned' )} {:else} — {/if}
{#if findingCount && findingCount > 0} {findingCount} {:else} 0 {/if} {#if run.error_message} {run.error_message} {:else} — {/if}
{t('admin.jobs.run_json', 'Run summary (JSON)')}
{JSON.stringify(
																				{
																					id: run.id,
																					status: run.status,
																					started_at: run.started_at,
																					last_progress_at: run.last_progress_at,
																					completed_at: run.completed_at,
																					stats: run.stats,
																					params: run.params,
																					cursor_hex: run.cursor_hex,
																					error_message: run.error_message
																				},
																				null,
																				2
																			)}

{t('admin.jobs.findings_title', 'Findings')}

{#if findingsErr}

{findingsErr}

{:else if !findings}

{t('common.loading', 'Loading…')}

{:else if findings.length === 0}

{t('admin.jobs.no_findings', 'No findings — clean run.')}

{:else} {#each findings as f (f.id)} {@const detail = (f.detail ?? {}) as Record< string, unknown >} {@const label = (detail.path as string | undefined) ?? (detail.name as string | undefined) ?? null} {/each}
{t('admin.jobs.col_kind', 'Kind')} {t('admin.jobs.col_severity', 'Severity')} {t('admin.jobs.col_resource', 'Resource')} {t('admin.jobs.col_detail', 'Detail')}
{f.kind} {f.severity} {#if label}
{label} {#if f.resource_id} {f.resource_id} {/if}
{:else} {f.resource_id ?? '—'} {/if}
{JSON.stringify(f.detail)}
{/if}
{/if}
{/if}
{#if purgeModal}
{ e.preventDefault(); void confirmPurge(); }} >

{t( 'admin.jobs.purge_body', 'Delete completed and failed run history older than the chosen number of days. Findings drop with their parent runs. Non-terminal runs (running, paused, cancel-requested) are always preserved.' )}

{/if}