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 ConfirmDialog from '$lib/components/ConfirmDialog.svelte';
|
||||||
import EmptyState from '$lib/components/EmptyState.svelte';
|
import EmptyState from '$lib/components/EmptyState.svelte';
|
||||||
import Skeleton from '$lib/components/Skeleton.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 FormField from '$lib/components/FormField.svelte';
|
||||||
import { toasts } from '$lib/stores/toast';
|
import { toasts } from '$lib/stores/toast';
|
||||||
import { t } from '$lib/i18n';
|
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) {
|
async function handleDeleteStage(stageId: string, name: string) {
|
||||||
try {
|
try {
|
||||||
await api.deleteStage(projectId, stageId);
|
await api.deleteStage(projectId, stageId);
|
||||||
@@ -225,23 +262,63 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Project info -->
|
<!-- 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 class="rounded-xl border border-[var(--border-primary)] bg-[var(--surface-card)] p-5 shadow-[var(--shadow-sm)]">
|
||||||
<div>
|
{#if editing}
|
||||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.port')}</p>
|
<div class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.port || '-'}</p>
|
<FormField label="Name *" name="editName" bind:value={editName} />
|
||||||
</div>
|
<FormField label="Image *" name="editImage" bind:value={editImage} />
|
||||||
<div>
|
<FormField label="Port" name="editPort" type="number" bind:value={editPort} />
|
||||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.registry')}</p>
|
<FormField label="Healthcheck Path" name="editHealthcheck" bind:value={editHealthcheck} placeholder="/api/health" />
|
||||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.registry || '-'}</p>
|
</div>
|
||||||
</div>
|
<div class="mt-4 flex items-center gap-2 justify-end">
|
||||||
<div>
|
<button
|
||||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.healthcheck')}</p>
|
type="button"
|
||||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{project.healthcheck || '-'}</p>
|
onclick={() => { editing = false; }}
|
||||||
</div>
|
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"
|
||||||
<div>
|
>
|
||||||
<p class="text-xs font-medium text-[var(--text-tertiary)] uppercase">{$t('projects.created')}</p>
|
<IconX size={14} />
|
||||||
<p class="mt-1 text-sm text-[var(--text-primary)]">{new Date(project.created_at).toLocaleDateString()}</p>
|
{$t('projects.cancel')}
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|
||||||
<!-- Stages & Instances -->
|
<!-- Stages & Instances -->
|
||||||
|
|||||||
Reference in New Issue
Block a user