-
- {#if tagsLoading}
-
-
- {$t('projectDetail.loadingTags')}
-
- {:else if availableTags.length > 0}
-
- {:else}
-
- {/if}
+ {#if editingStageId === stage.id}
+
+
+
+
+
+
+
+
+
+ {$t('projectDetail.autoDeployLabel')}
+
+
+
+ {$t('projectDetail.enableProxy')}
+
+
+
+
+
+
+
+
+ {:else}
+
+
+
{stage.name}
+ {stage.tag_pattern}
+ {#if stage.auto_deploy}
+ {$t('projectDetail.autoDeploy')}
+ {/if}
+ {#if stage.confirm}
+ {$t('projectDetail.requiresConfirm')}
+ {/if}
+ {#if !stage.enable_proxy}
+ {$t('projectDetail.noProxy')}
+ {/if}
+
+
+
+ {stageInstances.length} / {stage.max_instances} {$t('projectDetail.instances')}
+
+
+
+
+ {/if}
+
+
+ {#if deployStageId === stage.id && deployTag}
+
+
+
{$t('projectDetail.deployTag')}:
+
{deployTag}
+
+
+
+
+
{#if deployError}
{deployError}
@@ -671,6 +812,20 @@
oncancel={() => { showDeleteConfirm = false; }}
/>
+
{
+ const target = stageDeleteTarget;
+ stageDeleteTarget = null;
+ if (target) await handleDeleteStage(target.id, target.name);
+ }}
+ oncancel={() => { stageDeleteTarget = null; }}
+ />
+
{ accessListPickerOpen = false; }}
/>
+
+ { tagPickerOpen = false; }}
+ />
{/if}
diff --git a/web/src/routes/projects/[id]/env/+page.svelte b/web/src/routes/projects/[id]/env/+page.svelte
index 5fa2a11..37fae90 100644
--- a/web/src/routes/projects/[id]/env/+page.svelte
+++ b/web/src/routes/projects/[id]/env/+page.svelte
@@ -31,8 +31,68 @@
let envDeleteTarget = $state(null);
+ // Project-level env editing
+ let newProjectKey = $state('');
+ let newProjectValue = $state('');
+ let savingProject = $state(false);
+ let editingProjectKey = $state('');
+ let editProjectValue = $state('');
+ let projectEnvDeleteTarget = $state(null);
+
const projectId = $derived($page.params.id);
+ async function handleAddProjectEnv() {
+ if (!newProjectKey.trim()) return;
+ savingProject = true;
+ try {
+ const updated = { ...projectEnv, [newProjectKey.trim()]: newProjectValue };
+ await api.updateProject(projectId!, { env: JSON.stringify(updated) });
+ projectEnv = updated;
+ newProjectKey = '';
+ newProjectValue = '';
+ toasts.success($t('envEditor.envAdded'));
+ } catch (e) {
+ toasts.error(e instanceof Error ? e.message : $t('envEditor.addFailed'));
+ } finally {
+ savingProject = false;
+ }
+ }
+
+ function startEditProjectEnv(key: string) {
+ editingProjectKey = key;
+ editProjectValue = projectEnv[key] ?? '';
+ }
+
+ async function handleUpdateProjectEnv() {
+ if (!editingProjectKey) return;
+ savingProject = true;
+ try {
+ const updated = { ...projectEnv, [editingProjectKey]: editProjectValue };
+ await api.updateProject(projectId!, { env: JSON.stringify(updated) });
+ projectEnv = updated;
+ editingProjectKey = '';
+ toasts.success($t('envEditor.envUpdated'));
+ } catch (e) {
+ toasts.error(e instanceof Error ? e.message : $t('envEditor.updateFailed'));
+ } finally {
+ savingProject = false;
+ }
+ }
+
+ async function handleDeleteProjectEnv(key: string) {
+ savingProject = true;
+ try {
+ const { [key]: _, ...rest } = projectEnv;
+ await api.updateProject(projectId!, { env: JSON.stringify(rest) });
+ projectEnv = rest;
+ toasts.success($t('envEditor.envDeleted'));
+ } catch (e) {
+ toasts.error(e instanceof Error ? e.message : $t('envEditor.deleteFailed'));
+ } finally {
+ savingProject = false;
+ }
+ }
+
async function loadProject() {
if (stages.length === 0) loading = true;
error = '';
@@ -169,41 +229,42 @@
{error}
{:else}
-
-
-
-
-
-
+
{#if stages.length === 0}
{:else}
-
- {#if Object.keys(projectEnv).length > 0}
-
-
{$t('envEditor.projectDefaults')}
-
-
-
-
- | {$t('envEditor.key')} |
- {$t('envEditor.value')} |
- {$t('envEditor.source')} |
-
-
-
- {#each Object.entries(projectEnv) as [key, value] (key)}
-
+
+
- {/if}
+ {#if Object.keys(projectEnv).length === 0}
+
{$t('envEditor.noProjectEnv')}
+ {/if}
+
-
{$t('envEditor.stageOverrides')}
+
+
{$t('envEditor.stageOverrides')}
+
+
{#if envLoading}
@@ -346,3 +449,17 @@
}}
oncancel={() => { envDeleteTarget = null; }}
/>
+
+ {
+ const key = projectEnvDeleteTarget;
+ projectEnvDeleteTarget = null;
+ if (key) await handleDeleteProjectEnv(key);
+ }}
+ oncancel={() => { projectEnvDeleteTarget = null; }}
+/>