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
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:
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user