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.
95 lines
2.8 KiB
Svelte
95 lines
2.8 KiB
Svelte
<script lang="ts">
|
|
import { resolve } from '$app/paths';
|
|
import type { JobRow } from '$lib/api/types.js';
|
|
import { formatDateTime } from '$lib/modules/display.js';
|
|
import { jobKindTitle } from '$lib/operations/job-kind-label.js';
|
|
import { jobStatusRu, jobStatusBadgeVariant } from '$lib/ui-labels.js';
|
|
import { Badge } from '$lib/ui/core/badge/index.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: JobRow[];
|
|
moduleNameById: ReadonlyMap<string, string>;
|
|
loading?: boolean;
|
|
initialLoading?: boolean;
|
|
error?: string | null;
|
|
};
|
|
|
|
let {
|
|
items,
|
|
moduleNameById,
|
|
loading = false,
|
|
initialLoading = false,
|
|
error = null
|
|
}: Props = $props();
|
|
|
|
const columns = [
|
|
{ id: 'kind', label: 'Вид', sortable: true, sortValue: (j: JobRow) => j.kind },
|
|
{
|
|
id: 'status',
|
|
label: 'Статус',
|
|
sortable: true,
|
|
sortValue: (j: JobRow) => j.status
|
|
},
|
|
{
|
|
id: 'created',
|
|
label: 'Создана',
|
|
sortable: true,
|
|
sortValue: (j: JobRow) => j.created_at ?? ''
|
|
},
|
|
{ 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>Фоновые задачи ingest, refresh и apply</CardDescription>
|
|
</div>
|
|
<Button variant="outline" size="sm" href={resolve('/operations?tab=jobs')}>
|
|
Все
|
|
<ArrowRight class="size-3.5" />
|
|
</Button>
|
|
</CardHeader>
|
|
<CardContent class="p-4 pt-0">
|
|
<AppDataTable
|
|
columns={[...columns]}
|
|
rows={items}
|
|
rowKey={(j) => j.job_id}
|
|
loading={initialLoading || loading}
|
|
{error}
|
|
emptyTitle="Нет задач"
|
|
emptyDescription="Задачи появятся после refresh или деплоя."
|
|
>
|
|
{#snippet cell({ row: j, column })}
|
|
{#if column.id === 'kind'}
|
|
<span class="font-medium">{jobKindTitle(j, moduleNameById)}</span>
|
|
{:else if column.id === 'status'}
|
|
<Badge variant={jobStatusBadgeVariant(j.status)}>{jobStatusRu(j.status)}</Badge>
|
|
{:else if column.id === 'created'}
|
|
<span class="text-xs whitespace-nowrap text-muted-foreground">
|
|
{formatDateTime(j.created_at)}
|
|
</span>
|
|
{:else if column.id === 'actions'}
|
|
<Button variant="ghost" size="icon-sm" href={resolve('/operations?tab=jobs')}>
|
|
<ExternalLink class="size-3.5" />
|
|
</Button>
|
|
{/if}
|
|
{/snippet}
|
|
</AppDataTable>
|
|
</CardContent>
|
|
</Card>
|