feat: add last refreshed timestamp to modules and CDN sources
CI / changes (push) Successful in 6s
CI / openapi (push) Successful in 23s
CI / go (push) Successful in 45s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m14s
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) Successful in 16s
CI / docker-go-prime (push) Successful in 23s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m2s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 6m21s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m32s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m26s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m26s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m8s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m25s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m28s
CI / changes (push) Successful in 6s
CI / openapi (push) Successful in 23s
CI / go (push) Successful in 45s
CI / docker-web (deploy/docker/evobgp-web/Dockerfile, , evobgp-web) (push) Successful in 1m14s
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) Successful in 16s
CI / docker-go-prime (push) Successful in 23s
CI / docker-go (deploy/docker/evobgp-agent/Dockerfile, , evobgp-agent) (push) Successful in 1m2s
CI / docker-go (evobgp-all, 1, deploy/docker/gobinary/Dockerfile, , evobgp-all) (push) Successful in 6m21s
CI / docker-go (evobgp-api, 1, deploy/docker/gobinary/Dockerfile, , evobgp-api) (push) Successful in 1m32s
CI / docker-go (evobgp-deploy, 0, deploy/docker/gobinary/Dockerfile, , evobgp-deploy) (push) Successful in 1m26s
CI / docker-go (evobgp-ingest, 0, deploy/docker/gobinary/Dockerfile, , evobgp-ingest) (push) Successful in 1m26s
CI / docker-go (evobgp-node, 0, deploy/docker/gobinary/Dockerfile, , evobgp-node) (push) Successful in 1m8s
CI / docker-go (evobgp-render, 0, deploy/docker/gobinary/Dockerfile, , evobgp-render) (push) Successful in 1m25s
CI / docker-go (evobgp-scheduler, 0, deploy/docker/gobinary/Dockerfile, , evobgp-scheduler) (push) Successful in 1m28s
Introduced a new field, last_refreshed_at, to track the last successful update time for modules and CDN sources. Updated the database schema, API responses, and internal logic to support this feature. Enhanced the frontend to display the last refreshed timestamp, improving visibility into the data update status for users.
This commit is contained in:
@@ -18,6 +18,7 @@ export type ModuleRow = {
|
||||
cron_expr: string | null;
|
||||
default_community_id: string | null;
|
||||
doh_profile_id: string | null;
|
||||
last_refreshed_at: string | null;
|
||||
};
|
||||
export type ModulesResponse = Page<ModuleRow>;
|
||||
|
||||
@@ -71,6 +72,7 @@ export type CdnSource = {
|
||||
prefix_path: string;
|
||||
community_id: string | null;
|
||||
refresh_interval_sec: number | null;
|
||||
last_refreshed_at: string | null;
|
||||
};
|
||||
export type CdnSourceCreate = {
|
||||
url: string;
|
||||
|
||||
@@ -136,6 +136,13 @@
|
||||
return '—';
|
||||
}
|
||||
|
||||
function formatDateTime(value: string | null | undefined): string {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) return '—';
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) return '—';
|
||||
return parsed.toLocaleString('ru-RU');
|
||||
}
|
||||
|
||||
function toggleModuleSelection(id: string) {
|
||||
const next = new Set(selectedModuleIds);
|
||||
if (next.has(id)) next.delete(id);
|
||||
@@ -232,6 +239,7 @@
|
||||
<TableHead>Тип</TableHead>
|
||||
<TableHead>Приоритет</TableHead>
|
||||
<TableHead>Интервал</TableHead>
|
||||
<TableHead>Последнее обновление</TableHead>
|
||||
<TableHead>Статус</TableHead>
|
||||
<TableHead class="w-16"></TableHead>
|
||||
</TableRow>
|
||||
@@ -255,6 +263,9 @@
|
||||
<TableCell class="text-muted-foreground font-mono text-xs">
|
||||
{moduleIntervalLabel(m)}
|
||||
</TableCell>
|
||||
<TableCell class="text-muted-foreground text-sm whitespace-nowrap">
|
||||
{formatDateTime(m.last_refreshed_at)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{#if m.enabled}
|
||||
<Badge variant="default" class="text-xs">вкл</Badge>
|
||||
@@ -270,7 +281,7 @@
|
||||
</TableRow>
|
||||
{:else}
|
||||
<TableRow>
|
||||
<TableCell colspan={7} class="text-muted-foreground text-center py-8">
|
||||
<TableCell colspan={8} class="text-muted-foreground text-center py-8">
|
||||
{loading ? 'Загрузка…' : 'Нет модулей. Создайте первый.'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
||||
@@ -762,6 +762,13 @@
|
||||
return '—';
|
||||
}
|
||||
|
||||
function formatDateTime(value: string | null | undefined): string {
|
||||
if (typeof value !== 'string' || value.trim().length === 0) return '—';
|
||||
const parsed = new Date(value);
|
||||
if (Number.isNaN(parsed.getTime())) return '—';
|
||||
return parsed.toLocaleString('ru-RU');
|
||||
}
|
||||
|
||||
const activeTab = $derived.by(() => {
|
||||
if (!mod) return 'entries';
|
||||
switch (mod.type) {
|
||||
@@ -831,7 +838,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Module metadata -->
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-4">
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2 xl:grid-cols-5">
|
||||
<Card class="bg-chart-3/10 p-3">
|
||||
<p class="text-muted-foreground flex items-center gap-1.5 text-xs"><ArrowDownUp class="size-3.5" />Приоритет</p>
|
||||
<p class="font-semibold">{mod.priority}</p>
|
||||
@@ -850,6 +857,10 @@
|
||||
<p class="text-muted-foreground flex items-center gap-1.5 text-xs"><ShieldCheck class="size-3.5" />Community по умолч.</p>
|
||||
<p class="font-semibold text-sm break-all">{communityLabel(mod.default_community_id)}</p>
|
||||
</Card>
|
||||
<Card class="bg-muted/60 p-3">
|
||||
<p class="text-muted-foreground flex items-center gap-1.5 text-xs"><RefreshCw class="size-3.5" />Последнее обновление</p>
|
||||
<p class="font-semibold text-sm whitespace-nowrap">{formatDateTime(mod.last_refreshed_at)}</p>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
@@ -1058,6 +1069,7 @@
|
||||
<TableHead>Тип</TableHead>
|
||||
<TableHead>Community</TableHead>
|
||||
<TableHead>Интервал</TableHead>
|
||||
<TableHead>Последнее обновление</TableHead>
|
||||
<TableHead class="w-20"></TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
@@ -1089,6 +1101,9 @@
|
||||
? `${src.refresh_interval_sec}с`
|
||||
: '—'}
|
||||
</TableCell>
|
||||
<TableCell class="text-muted-foreground text-sm whitespace-nowrap">
|
||||
{formatDateTime(src.last_refreshed_at)}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div class="flex gap-1">
|
||||
<Button variant="ghost" size="icon-sm" onclick={() => openCdnEdit(src)}><Pencil class="size-3.5" /></Button>
|
||||
@@ -1098,7 +1113,7 @@
|
||||
</TableRow>
|
||||
{:else}
|
||||
<TableRow>
|
||||
<TableCell colspan={6} class="text-muted-foreground text-center py-6">Нет источников</TableCell>
|
||||
<TableCell colspan={7} class="text-muted-foreground text-center py-6">Нет источников</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
|
||||
Reference in New Issue
Block a user