feat: enhance job filtering and display functionality in operations page. Introduce job search and filter options, allowing users to refine job listings by status, kind, and module. Update OperationsJobsTab to reflect total fetched jobs and improve overall user experience with better layout and responsiveness.
CI / changes (push) Successful in 6s
CI / openapi (push) Has been skipped
CI / go (push) Has been skipped
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m9s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, evobgp-all, evobgp-web-all) (push) Successful in 1m7s
CI / docker-bird (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / docker-go-prime (push) Has been skipped
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Has been skipped
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Has been skipped
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Has been skipped
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Has been skipped
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Has been skipped
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Has been skipped
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Has been skipped
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Has been skipped

This commit is contained in:
Denozordec
2026-04-07 00:41:29 +07:00
parent 4aaaa30b1b
commit e1c4cbce93
2 changed files with 217 additions and 22 deletions
@@ -2,7 +2,7 @@
import type { RevisionDiff, RevisionPrefix, RevisionRow } from '$lib/api/types.js';
import { Button } from '$lib/components/ui/button/index.js';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '$lib/components/ui/card/index.js';
import { ScrollArea } from '$lib/components/ui/scroll-area/index.js';
import { sortRevisionDiffItems } from '$lib/sort-prefixes.js';
type Props = {
revisions: RevisionRow[];
@@ -29,27 +29,52 @@
}: Props = $props();
/** Бэкенд отдаёт `prefixes.added` / `prefixes.removed`; верхний уровень added/removed — опционально. */
function diffAdded(d: RevisionDiff | null): (string | RevisionPrefix)[] {
function diffAddedRaw(d: RevisionDiff | null): (string | RevisionPrefix)[] {
if (!d) return [];
if (d.prefixes && Array.isArray(d.prefixes.added)) return d.prefixes.added;
if (Array.isArray(d.added)) return d.added;
return [];
}
function diffRemoved(d: RevisionDiff | null): (string | RevisionPrefix)[] {
function diffRemovedRaw(d: RevisionDiff | null): (string | RevisionPrefix)[] {
if (!d) return [];
if (d.prefixes && Array.isArray(d.prefixes.removed)) return d.prefixes.removed;
if (Array.isArray(d.removed)) return d.removed;
return [];
}
let addedSorted = $derived(diffData ? sortRevisionDiffItems(diffAddedRaw(diffData)) : []);
let removedSorted = $derived(diffData ? sortRevisionDiffItems(diffRemovedRaw(diffData)) : []);
let scrollAddedEl: HTMLDivElement | null = $state(null);
let scrollRemovedEl: HTMLDivElement | null = $state(null);
let syncScrollLock = false;
function syncFromAdded() {
if (syncScrollLock || !scrollAddedEl || !scrollRemovedEl) return;
syncScrollLock = true;
scrollRemovedEl.scrollTop = scrollAddedEl.scrollTop;
queueMicrotask(() => {
syncScrollLock = false;
});
}
function syncFromRemoved() {
if (syncScrollLock || !scrollAddedEl || !scrollRemovedEl) return;
syncScrollLock = true;
scrollAddedEl.scrollTop = scrollRemovedEl.scrollTop;
queueMicrotask(() => {
syncScrollLock = false;
});
}
</script>
<Card>
<Card class="min-h-0">
<CardHeader>
<CardTitle class="text-base">Сравнение ревизий</CardTitle>
<CardDescription>Выберите ID двух ревизий для сравнения</CardDescription>
</CardHeader>
<CardContent class="space-y-4">
<CardContent class="min-h-0 space-y-4">
<div class="flex flex-col gap-2 sm:flex-row">
<select
value={diffRevA}
@@ -77,26 +102,84 @@
</div>
{#if diffData}
<div class="grid gap-3 sm:grid-cols-2">
<div>
<p class="mb-2 text-sm font-medium text-green-600">+ Добавлено ({diffAdded(diffData).length})</p>
<ScrollArea class="max-h-64 rounded border p-2">
{#each diffAdded(diffData) as p, idx (`added-${idx}-${typeof p === 'string' ? p : (p as { prefix?: string }).prefix ?? 'item'}`)}
<p class="font-mono text-xs break-all">{typeof p === 'string' ? p : (p as { prefix?: string }).prefix ?? JSON.stringify(p)}</p>
<div
class="border-border grid min-h-0 grid-cols-1 overflow-hidden rounded-md border sm:grid-cols-2"
style="scrollbar-gutter: stable;"
>
<!-- + Добавлено -->
<div class="border-border flex min-h-0 min-w-0 flex-col border-b sm:border-r sm:border-b-0">
<div
class="border-border bg-muted/60 flex shrink-0 items-center border-b px-3 py-2 font-mono text-xs font-semibold text-green-600 dark:text-green-400"
>
<span class="text-muted-foreground mr-2 w-10 shrink-0 select-none text-right">+</span>
<span>Добавлено ({addedSorted.length})</span>
</div>
<div
bind:this={scrollAddedEl}
class="bg-background/50 max-h-[min(28rem,70vh)] min-h-0 overflow-x-auto overflow-y-scroll overscroll-contain"
onscroll={syncFromAdded}
>
{#if addedSorted.length === 0}
<p class="text-muted-foreground p-3 font-mono text-xs">Нет изменений</p>
{:else}
<p class="text-muted-foreground text-xs">Нет изменений</p>
{/each}
</ScrollArea>
<table class="w-full border-collapse font-mono text-xs">
<tbody>
{#each addedSorted as line, i (`a-${i}-${line}`)}
<tr
class="border-border/50 hover:bg-muted/30 border-b border-l-2 border-l-green-500/80 bg-green-500/[0.08] dark:bg-green-500/15"
>
<td
class="text-muted-foreground w-10 shrink-0 border-r border-transparent py-0.5 pr-1 pl-2 text-right align-top text-[11px] tabular-nums select-none"
>
{i + 1}
</td>
<td class="text-foreground max-w-0 break-all py-0.5 pr-3 pl-1 whitespace-pre-wrap">
{line}
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
</div>
<div>
<p class="mb-2 text-sm font-medium text-red-600">- Удалено ({diffRemoved(diffData).length})</p>
<ScrollArea class="max-h-64 rounded border p-2">
{#each diffRemoved(diffData) as p, idx (`removed-${idx}-${typeof p === 'string' ? p : (p as { prefix?: string }).prefix ?? 'item'}`)}
<p class="font-mono text-xs break-all">{typeof p === 'string' ? p : (p as { prefix?: string }).prefix ?? JSON.stringify(p)}</p>
<!-- Удалено -->
<div class="flex min-h-0 min-w-0 flex-col">
<div
class="border-border bg-muted/60 flex shrink-0 items-center border-b px-3 py-2 font-mono text-xs font-semibold text-red-600 dark:text-red-400"
>
<span class="text-muted-foreground mr-2 w-10 shrink-0 select-none text-right"></span>
<span>Удалено ({removedSorted.length})</span>
</div>
<div
bind:this={scrollRemovedEl}
class="bg-background/50 max-h-[min(28rem,70vh)] min-h-0 overflow-x-auto overflow-y-scroll overscroll-contain"
onscroll={syncFromRemoved}
>
{#if removedSorted.length === 0}
<p class="text-muted-foreground p-3 font-mono text-xs">Нет изменений</p>
{:else}
<p class="text-muted-foreground text-xs">Нет изменений</p>
{/each}
</ScrollArea>
<table class="w-full border-collapse font-mono text-xs">
<tbody>
{#each removedSorted as line, i (`r-${i}-${line}`)}
<tr
class="border-border/50 hover:bg-muted/30 border-b border-l-2 border-l-red-500/80 bg-red-500/[0.08] dark:bg-red-500/15"
>
<td
class="text-muted-foreground w-10 shrink-0 border-r border-transparent py-0.5 pr-1 pl-2 text-right align-top text-[11px] tabular-nums select-none"
>
{i + 1}
</td>
<td class="text-foreground max-w-0 break-all py-0.5 pr-3 pl-1 whitespace-pre-wrap">
{line}
</td>
</tr>
{/each}
</tbody>
</table>
{/if}
</div>
</div>
</div>
{/if}