CI / changes (push) Successful in 9s
CI / openapi (push) Has been skipped
CI / commitlint (push) Has been skipped
CI / web (push) Successful in 42s
CI / go (push) Has been skipped
CI / bird2 (push) Has been skipped
CI / release (push) Successful in 3m57s
- Refactored state management for modules, revisions, peers, speakers, and jobs, enhancing loading and error handling. - Updated imports to utilize core UI components for better maintainability and consistency. - Introduced derived states for KPI cards, providing real-time insights into system metrics. - Improved loading function to fetch data efficiently and handle errors gracefully. - Enhanced UI layout with new icons and dynamic descriptions based on last updated timestamps.
90 lines
2.6 KiB
Svelte
90 lines
2.6 KiB
Svelte
<script lang="ts">
|
|
import { resolve } from '$app/paths';
|
|
import type { RevisionRow } from '$lib/api/types.js';
|
|
import { formatDateTime } from '$lib/modules/display.js';
|
|
import { Button } from '$lib/ui/core/button/index.js';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription
|
|
} from '$lib/ui/core/card/index.js';
|
|
import AppDataTable from '$lib/ui/patterns/data-table/app-data-table.svelte';
|
|
import ArrowRight from '@lucide/svelte/icons/arrow-right';
|
|
import ExternalLink from '@lucide/svelte/icons/external-link';
|
|
|
|
type Props = {
|
|
items: RevisionRow[];
|
|
loading?: boolean;
|
|
initialLoading?: boolean;
|
|
error?: string | null;
|
|
};
|
|
|
|
let { items, loading = false, initialLoading = false, error = null }: Props = $props();
|
|
|
|
const columns = [
|
|
{
|
|
id: 'id',
|
|
label: 'ID',
|
|
sortable: true,
|
|
sortValue: (rev: RevisionRow) => rev.id
|
|
},
|
|
{
|
|
id: 'created',
|
|
label: 'Создана',
|
|
sortable: true,
|
|
sortValue: (rev: RevisionRow) => rev.created_at ?? ''
|
|
},
|
|
{
|
|
id: 'prefixes',
|
|
label: 'Префиксов',
|
|
sortable: true,
|
|
sortValue: (rev: RevisionRow) => rev.materialized_prefix_count ?? 0
|
|
},
|
|
{ id: 'actions', label: '', class: 'w-10' }
|
|
] as const;
|
|
</script>
|
|
|
|
<Card>
|
|
<CardHeader
|
|
class="flex flex-col gap-3 border-b py-3 sm:flex-row sm:items-center sm:justify-between"
|
|
>
|
|
<div class="min-w-0 flex-1">
|
|
<CardTitle class="text-base">Последние ревизии</CardTitle>
|
|
<CardDescription>Снимки конфигурации BIRD после обновления модулей</CardDescription>
|
|
</div>
|
|
<Button variant="outline" size="sm" href={resolve('/operations')}>
|
|
Все
|
|
<ArrowRight class="size-3.5" />
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent class="p-4 pt-0">
|
|
<AppDataTable
|
|
columns={[...columns]}
|
|
rows={items}
|
|
rowKey={(rev) => rev.id}
|
|
loading={initialLoading || loading}
|
|
{error}
|
|
emptyTitle="Нет ревизий"
|
|
emptyDescription="Ревизии появятся после обновления модулей."
|
|
>
|
|
{#snippet cell({ row: rev, column })}
|
|
{#if column.id === 'id'}
|
|
<span class="font-mono text-xs">{rev.id.slice(0, 8)}…</span>
|
|
{:else if column.id === 'created'}
|
|
<span class="text-sm whitespace-nowrap text-muted-foreground">
|
|
{formatDateTime(rev.created_at)}
|
|
</span>
|
|
{:else if column.id === 'prefixes'}
|
|
<span class="tabular-nums">{rev.materialized_prefix_count}</span>
|
|
{:else if column.id === 'actions'}
|
|
<Button variant="ghost" size="icon-sm" href={resolve('/operations')}>
|
|
<ExternalLink class="size-3.5" />
|
|
</Button>
|
|
{/if}
|
|
{/snippet}
|
|
</AppDataTable>
|
|
</CardContent>
|
|
</Card>
|