feat: show local Docker images on project detail page

- Add GET /api/projects/{id}/images endpoint returning local images matching the project
- Add ListImagesByRef with tag, size, and created timestamp to Docker client
- Display images table on project page with tag, ID (truncated), size (MB), and created date
- Only shown when Docker is available and images exist locally
This commit is contained in:
2026-04-05 13:56:55 +03:00
parent 198bdb856c
commit ac3132d172
8 changed files with 112 additions and 7 deletions
+37 -1
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import { goto } from '$app/navigation';
import type { Project, Stage, Instance, Deploy } from '$lib/types';
import type { Project, Stage, Instance, Deploy, LocalImage } from '$lib/types';
import * as api from '$lib/api';
import StatusBadge from '$lib/components/StatusBadge.svelte';
import InstanceCard from '$lib/components/InstanceCard.svelte';
@@ -157,6 +157,7 @@
}
let tagsLoading = $state(false);
let settingsDomain = $state('');
let localImages = $state<LocalImage[]>([]);
let showDeleteConfirm = $state(false);
@@ -198,6 +199,10 @@
const settings = await api.getSettings();
settingsDomain = settings.domain ?? '';
} catch { /* non-critical */ }
try {
localImages = await api.listProjectImages(projectId);
} catch { localImages = []; }
} catch (e) {
error = e instanceof Error ? e.message : $t('projectDetail.loadFailed');
} finally {
@@ -582,6 +587,37 @@
{/if}
</div>
<!-- Local Docker Images -->
{#if localImages.length > 0}
<div>
<h2 class="text-lg font-semibold text-[var(--text-primary)]">{$t('projectDetail.localImages')}</h2>
<div class="mt-4 overflow-x-auto rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] shadow-[var(--shadow-sm)]">
<table class="min-w-full divide-y divide-[var(--border-primary)]">
<thead class="bg-[var(--surface-card-hover)]">
<tr>
<th class="px-4 py-2.5 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projectDetail.imageTag')}</th>
<th class="px-4 py-2.5 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projectDetail.imageId')}</th>
<th class="px-4 py-2.5 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projectDetail.imageSize')}</th>
<th class="px-4 py-2.5 text-left text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projectDetail.imageCreated')}</th>
</tr>
</thead>
<tbody class="divide-y divide-[var(--border-secondary)]">
{#each localImages as img (img.id + img.tag)}
<tr class="hover:bg-[var(--surface-card-hover)] transition-colors">
<td class="px-4 py-2.5">
<span class="rounded-full bg-[var(--surface-card-hover)] px-2 py-0.5 text-xs font-mono text-[var(--text-secondary)]">{img.tag || 'untagged'}</span>
</td>
<td class="px-4 py-2.5 text-xs font-mono text-[var(--text-tertiary)]">{img.id.substring(7, 19)}</td>
<td class="px-4 py-2.5 text-sm text-[var(--text-secondary)]">{(img.size / (1024 * 1024)).toFixed(1)} MB</td>
<td class="px-4 py-2.5 text-sm text-[var(--text-tertiary)]">{new Date(img.created * 1000).toLocaleDateString()}</td>
</tr>
{/each}
</tbody>
</table>
</div>
</div>
{/if}
<!-- Deploy History Timeline -->
<div>
<h2 class="text-lg font-semibold text-[var(--text-primary)]">{$t('projectDetail.recentDeploys')}</h2>