feat: project-scoped Docker image prune, conflict fix, deploy toggle, access list picker

- Image prune only removes images matching project image refs, skips active instances
- Add ListImagesByRef and RemoveImage to Docker client
- Fix 409 conflict: use listProjects instead of duplicate POST
- Add "Deploy immediately" toggle to Quick Deploy (off by default)
- Replace raw access list ID with EntityPicker on project edit form
- Trigger proxy resync on access list change
- Fix stage form layout: single responsive row
- Fix empty port default on project creation
- Improve inspect error message for remote Docker
This commit is contained in:
2026-04-05 13:49:20 +03:00
parent a830378c5b
commit 5577851f22
9 changed files with 191 additions and 17 deletions
+31 -1
View File
@@ -1,5 +1,5 @@
<script lang="ts">
import { getSettings, updateSettings, getWebhookUrl, regenerateWebhookUrl, testDnsConnection, listDnsZones } from '$lib/api';
import { getSettings, updateSettings, getWebhookUrl, regenerateWebhookUrl, testDnsConnection, listDnsZones, pruneImages } from '$lib/api';
import type { EntityPickerItem } from '$lib/types';
import FormField from '$lib/components/FormField.svelte';
import EntityPicker from '$lib/components/EntityPicker.svelte';
@@ -38,8 +38,19 @@
let zoneName = $state('');
let testingDns = $state(false);
let pruning = $state(false);
let errors = $state<Record<string, string>>({});
async function handlePruneImages() {
pruning = true;
try {
const result = await pruneImages();
toasts.success($t('settings.pruneResult', { count: String(result.images_removed), mb: String(result.space_reclaimed_mb) }));
} catch (err) {
toasts.error(err instanceof Error ? err.message : $t('settings.pruneFailed'));
} finally { pruning = false; }
}
// Convert Go duration string (e.g., "5m", "300s", "1h") to seconds string.
function parseDurationToSeconds(dur: string): string {
if (!dur) return '60';
@@ -343,6 +354,25 @@
</div>
</div>
<!-- Docker Image Cleanup -->
<div class="mt-6 border-t border-[var(--border-primary)] pt-4">
<h3 class="mb-1 text-sm font-semibold text-[var(--text-primary)]">{$t('settings.dockerCleanup')}</h3>
<p class="mb-3 text-xs text-[var(--text-tertiary)]">{$t('settings.dockerCleanupHelp')}</p>
<button
type="button"
onclick={handlePruneImages}
disabled={pruning}
class="inline-flex items-center gap-2 rounded-lg border border-[var(--color-danger)] px-4 py-2 text-sm font-medium text-[var(--color-danger)] hover:bg-[var(--color-danger)] hover:text-white disabled:opacity-50 transition-colors"
>
{#if pruning}
<IconLoader size={16} />
{$t('settings.pruning')}
{:else}
{$t('settings.pruneImages')}
{/if}
</button>
</div>
<!-- DNS Configuration -->
<div class="mt-6 border-t border-[var(--border-primary)] pt-4">
<h3 class="mb-3 text-sm font-semibold text-[var(--text-primary)]">{$t('settingsGeneral.dnsConfig')}</h3>