fix: untag reviewed image names during prune (#1836)

This commit is contained in:
Anso
2026-08-20 07:53:57 -04:00
committed by GitHub
parent c6f36575b7
commit 01ffbdbfc7
12 changed files with 650 additions and 97 deletions
+19 -10
View File
@@ -126,16 +126,25 @@ function PrunePlanPreview({
{plan.items.length} {plan.items.length === 1 ? 'item' : 'items'}
{plan.reclaimableBytes > 0 ? ` · ${formatBytes(plan.reclaimableBytes)}` : ''}
</p>
<ul className="max-h-40 overflow-y-auto space-y-1 font-mono text-[12px] text-stat-subtitle/90">
{shown.map((item) => (
<li key={`${item.target}:${item.id}`} className="block truncate">
<span className="text-stat-subtitle/60">{item.target}</span>
{' · '}
{item.name}
{item.sizeBytes != null && item.sizeBytes > 0 ? ` · ${formatBytes(item.sizeBytes)}` : ''}
</li>
))}
</ul>
<ScrollArea type="hover" className="max-h-40">
<ul className="space-y-1 font-mono text-[12px] text-stat-subtitle/90">
{shown.map((item) => (
<li key={`${item.target}:${item.id}`} className="block">
<span className="block truncate">
<span className="text-stat-subtitle/60">{item.target}</span>
{' · '}
{item.name}
{item.sizeBytes != null && item.sizeBytes > 0 ? ` · ${formatBytes(item.sizeBytes)}` : ''}
</span>
{item.target === 'images' && item.image.references
.filter((ref) => ref !== item.name)
.map((ref) => (
<span key={ref} className="block break-all">{ref}</span>
))}
</li>
))}
</ul>
</ScrollArea>
{remaining > 0 && (
<p className="text-xs text-stat-subtitle/70">and {remaining} more</p>
)}
@@ -241,6 +241,43 @@ describe('ResourcesView', () => {
expect(body.planFingerprint).toBe('fp-test');
});
it('discloses extra repository tags in the prune confirm list', async () => {
mockedFetch.mockImplementation((url: string, opts?: RequestInit) => {
if (url === '/system/prune/plan' && opts?.method === 'POST') {
return Promise.resolve(jsonResponse(samplePrunePlan({
items: [{
target: 'images',
id: 'img-multi',
name: 'ghcr.io/example/very-long-app-name:1.5.2-build.1844',
sizeBytes: 1000,
managed: false,
reason: 'Image is not used by any container',
image: {
references: [
'ghcr.io/example/very-long-app-name:1.5.2-build.1844',
'ghcr.io/example/very-long-app-name:latest',
],
},
}],
})));
}
if (url === '/system/resources') {
return Promise.resolve(jsonResponse({ images: [], volumes: [], networks: [] }));
}
return Promise.resolve(jsonResponse({}));
});
const user = userEvent.setup();
render(<ResourcesView />);
await waitFor(() => expect(mockedFetch).toHaveBeenCalledWith('/system/resources'));
await user.click(screen.getByRole('button', { name: /Prune Unused Images/ }));
const extraRef = await screen.findByText('ghcr.io/example/very-long-app-name:latest');
expect(extraRef).toHaveClass('break-all');
expect(extraRef).not.toHaveClass('truncate');
expect(screen.getByText(/1\.5\.2-build\.1844/)).toBeInTheDocument();
});
it('surfaces the server error on a failed prune instead of a false success (M-2)', async () => {
mockedFetch.mockImplementation((url: string, opts?: RequestInit) => {
if (url === '/system/prune/plan' && opts?.method === 'POST') {