feat: inline project editing (name, image, port, healthcheck)
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
import ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||
import EmptyState from '$lib/components/EmptyState.svelte';
|
||||
import Skeleton from '$lib/components/Skeleton.svelte';
|
||||
import { IconTrash, IconKey, IconHardDrive, IconDeploy, IconChevronRight, IconClock, IconTag, IconLoader, IconPlus } from '$lib/components/icons';
|
||||
import { IconTrash, IconKey, IconHardDrive, IconDeploy, IconChevronRight, IconClock, IconTag, IconLoader, IconPlus, IconEdit, IconCheck, IconX } from '$lib/components/icons';
|
||||
import FormField from '$lib/components/FormField.svelte';
|
||||
import { toasts } from '$lib/stores/toast';
|
||||
import { t } from '$lib/i18n';
|
||||
@@ -55,6 +55,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Edit project
|
||||
let editing = $state(false);
|
||||
let editName = $state('');
|
||||
let editImage = $state('');
|
||||
let editPort = $state('');
|
||||
let editHealthcheck = $state('');
|
||||
let saving = $state(false);
|
||||
|
||||
function startEditing() {
|
||||
if (!project) return;
|
||||
editName = project.name;
|
||||
editImage = project.image;
|
||||
editPort = String(project.port || '');
|
||||
editHealthcheck = project.healthcheck || '';
|
||||
editing = true;
|
||||
}
|
||||
|
||||
async function saveProject() {
|
||||
if (!editName.trim() || !editImage.trim()) return;
|
||||
saving = true;
|
||||
try {
|
||||
await api.updateProject(projectId, {
|
||||
name: editName.trim(),
|
||||
image: editImage.trim(),
|
||||
port: parseInt(editPort) || 0,
|
||||
healthcheck: editHealthcheck.trim(),
|
||||
});
|
||||
toasts.success('Project updated');
|
||||
editing = false;
|
||||
await loadProject();
|
||||
} catch (e) {
|
||||
toasts.error(e instanceof Error ? e.message : 'Failed to update project');
|
||||
} finally {
|
||||
saving = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteStage(stageId: string, name: string) {
|
||||
try {
|
||||
await api.deleteStage(projectId, stageId);
|
||||
@@ -225,23 +262,63 @@
|
||||
</div>
|
||||
|
||||
<!-- Project info -->
|
||||
<div class="grid grid-cols-2 gap-4 rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)] sm:grid-cols-4">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.port')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.port || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.registry')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.registry || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.healthcheck')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.healthcheck || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.created')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{new Date(project.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
<div class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)]">
|
||||
{#if editing}
|
||||
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
<FormField label="Name *" name="editName" bind:value={editName} />
|
||||
<FormField label="Image *" name="editImage" bind:value={editImage} />
|
||||
<FormField label="Port" name="editPort" type="number" bind:value={editPort} />
|
||||
<FormField label="Healthcheck Path" name="editHealthcheck" bind:value={editHealthcheck} placeholder="/api/health" />
|
||||
</div>
|
||||
<div class="mt-4 flex items-center gap-2 justify-end">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => { editing = false; }}
|
||||
class="inline-flex items-center gap-1.5 rounded-lg border border-[var(--border-primary)] px-3 py-1.5 text-sm font-medium text-[var(--text-secondary)] hover:bg-[var(--surface-card-hover)] transition-colors"
|
||||
>
|
||||
<IconX size={14} />
|
||||
{$t('projects.cancel')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onclick={saveProject}
|
||||
disabled={saving || !editName.trim() || !editImage.trim()}
|
||||
class="inline-flex items-center gap-1.5 rounded-lg bg-[var(--color-brand-600)] px-3 py-1.5 text-sm font-medium text-white hover:bg-[var(--color-brand-700)] disabled:opacity-50 transition-colors"
|
||||
>
|
||||
<IconCheck size={14} />
|
||||
{saving ? 'Saving...' : $t('common.save')}
|
||||
</button>
|
||||
</div>
|
||||
{:else}
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="grid grid-cols-2 gap-4 flex-1 sm:grid-cols-4">
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.port')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.port || 'Auto'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.healthcheck')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.healthcheck || 'Auto'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.registry')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.registry || '-'}</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.created')}</p>
|
||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{new Date(project.created_at).toLocaleDateString()}</p>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
onclick={startEditing}
|
||||
title={$t('common.edit')}
|
||||
class="rounded-lg p-2 text-[var(--text-tertiary)] hover:bg-[var(--surface-card-hover)] hover:text-[var(--text-link)] transition-colors"
|
||||
>
|
||||
<IconEdit size={16} />
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Stages & Instances -->
|
||||
|
||||
Reference in New Issue
Block a user