fix: match original table UI with immediate per-action submission
All checks were successful
Check / check (pull_request) Successful in 4s
All checks were successful
Check / check (pull_request) Successful in 4s
Replace the Save All workflow with the original per-action behavior:
- Edit row: shows Save/Cancel buttons, submits full set immediately
- Delete row: shows confirmation dialog, submits full set immediately
- Add row: submits full set immediately on Add click
Moves Alpine.js logic into a proper envVarEditor component in
app-detail.js. Initializes env var data from hidden span elements
with data attributes for safe HTML escaping.
All actions collect the complete env var set and POST to the single
bulk endpoint POST /apps/{id}/env — no Save All button needed.
This commit is contained in:
@@ -6,6 +6,68 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
document.addEventListener("alpine:init", () => {
|
document.addEventListener("alpine:init", () => {
|
||||||
|
// ============================================
|
||||||
|
// Environment Variable Editor Component
|
||||||
|
// ============================================
|
||||||
|
Alpine.data("envVarEditor", () => ({
|
||||||
|
vars: [],
|
||||||
|
editIdx: -1,
|
||||||
|
editKey: "",
|
||||||
|
editVal: "",
|
||||||
|
|
||||||
|
init() {
|
||||||
|
this.vars = Array.from(this.$el.querySelectorAll(".env-init")).map(
|
||||||
|
(span) => ({
|
||||||
|
key: span.dataset.key,
|
||||||
|
value: span.dataset.value,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
startEdit(i) {
|
||||||
|
this.editIdx = i;
|
||||||
|
this.editKey = this.vars[i].key;
|
||||||
|
this.editVal = this.vars[i].value;
|
||||||
|
},
|
||||||
|
|
||||||
|
saveEdit(i) {
|
||||||
|
this.vars[i] = { key: this.editKey, value: this.editVal };
|
||||||
|
this.editIdx = -1;
|
||||||
|
this.submitAll();
|
||||||
|
},
|
||||||
|
|
||||||
|
removeVar(i) {
|
||||||
|
if (!window.confirm("Delete this environment variable?")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vars.splice(i, 1);
|
||||||
|
this.submitAll();
|
||||||
|
},
|
||||||
|
|
||||||
|
addVar(keyEl, valEl) {
|
||||||
|
const k = keyEl.value.trim();
|
||||||
|
const v = valEl.value.trim();
|
||||||
|
|
||||||
|
if (!k) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.vars.push({ key: k, value: v });
|
||||||
|
this.submitAll();
|
||||||
|
},
|
||||||
|
|
||||||
|
submitAll() {
|
||||||
|
this.$refs.bulkData.value = this.vars
|
||||||
|
.map((e) => e.key + "=" + e.value)
|
||||||
|
.join("\n");
|
||||||
|
this.$refs.bulkForm.submit();
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// ============================================
|
||||||
|
// App Detail Page Component
|
||||||
|
// ============================================
|
||||||
Alpine.data("appDetail", (config) => ({
|
Alpine.data("appDetail", (config) => ({
|
||||||
appId: config.appId,
|
appId: config.appId,
|
||||||
currentDeploymentId: config.initialDeploymentId,
|
currentDeploymentId: config.initialDeploymentId,
|
||||||
|
|||||||
@@ -101,39 +101,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Environment Variables -->
|
<!-- Environment Variables -->
|
||||||
<div class="card p-6 mb-6" x-data="{
|
<div class="card p-6 mb-6" x-data="envVarEditor()">
|
||||||
vars: [],
|
|
||||||
newKey: '',
|
|
||||||
newValue: '',
|
|
||||||
init() {
|
|
||||||
const text = this.$refs.envVarsField.value;
|
|
||||||
if (!text.trim()) return;
|
|
||||||
this.vars = text.split('\n')
|
|
||||||
.map(l => l.trim())
|
|
||||||
.filter(l => l !== '')
|
|
||||||
.map(l => {
|
|
||||||
const idx = l.indexOf('=');
|
|
||||||
if (idx === -1) return null;
|
|
||||||
return { key: l.substring(0, idx), value: l.substring(idx + 1), editing: false };
|
|
||||||
})
|
|
||||||
.filter(v => v !== null);
|
|
||||||
},
|
|
||||||
addVar() {
|
|
||||||
if (this.newKey.trim() === '') return;
|
|
||||||
this.vars.push({ key: this.newKey.trim(), value: this.newValue, editing: false });
|
|
||||||
this.newKey = '';
|
|
||||||
this.newValue = '';
|
|
||||||
},
|
|
||||||
removeVar(index) {
|
|
||||||
this.vars.splice(index, 1);
|
|
||||||
},
|
|
||||||
prepareSubmit() {
|
|
||||||
this.$refs.envVarsField.value = this.vars.map(v => v.key + '=' + v.value).join('\n');
|
|
||||||
}
|
|
||||||
}">
|
|
||||||
<h2 class="section-title mb-4">Environment Variables</h2>
|
<h2 class="section-title mb-4">Environment Variables</h2>
|
||||||
<form method="POST" action="/apps/{{.App.ID}}/env" @submit="prepareSubmit()">
|
{{range .EnvVars}}<span class="env-init hidden" data-key="{{.Key}}" data-value="{{.Value}}"></span>{{end}}
|
||||||
{{ .CSRFField }}
|
|
||||||
<template x-if="vars.length > 0">
|
<template x-if="vars.length > 0">
|
||||||
<div class="overflow-x-auto mb-4">
|
<div class="overflow-x-auto mb-4">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
@@ -145,27 +115,28 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody class="table-body">
|
<tbody class="table-body">
|
||||||
<template x-for="(v, index) in vars" :key="index">
|
<template x-for="(env, idx) in vars" :key="idx">
|
||||||
<tr>
|
<tr>
|
||||||
<template x-if="!v.editing">
|
<template x-if="editIdx !== idx">
|
||||||
<td class="font-mono font-medium" x-text="v.key"></td>
|
<td class="font-mono font-medium" x-text="env.key"></td>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="!v.editing">
|
<template x-if="editIdx !== idx">
|
||||||
<td class="font-mono text-gray-500" x-text="v.value"></td>
|
<td class="font-mono text-gray-500" x-text="env.value"></td>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="!v.editing">
|
<template x-if="editIdx !== idx">
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<button type="button" @click="v.editing = true" class="text-primary-600 hover:text-primary-800 text-sm mr-2">Edit</button>
|
<button @click="startEdit(idx)" class="text-primary-600 hover:text-primary-800 text-sm mr-2">Edit</button>
|
||||||
<button type="button" @click="removeVar(index)" class="text-error-500 hover:text-error-700 text-sm">Delete</button>
|
<button @click="removeVar(idx)" class="text-error-500 hover:text-error-700 text-sm">Delete</button>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
<template x-if="v.editing">
|
<template x-if="editIdx === idx">
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<div class="flex gap-2 items-center">
|
<form @submit.prevent="saveEdit(idx)" class="flex gap-2 items-center">
|
||||||
<input type="text" x-model="v.key" required class="input flex-1 font-mono text-sm">
|
<input type="text" x-model="editKey" required class="input flex-1 font-mono text-sm">
|
||||||
<input type="text" x-model="v.value" required class="input flex-1 font-mono text-sm">
|
<input type="text" x-model="editVal" required class="input flex-1 font-mono text-sm">
|
||||||
<button type="button" @click="v.editing = false" class="btn-primary text-sm">Done</button>
|
<button type="submit" class="btn-primary text-sm">Save</button>
|
||||||
</div>
|
<button type="button" @click="editIdx = -1" class="text-gray-500 hover:text-gray-700 text-sm">Cancel</button>
|
||||||
|
</form>
|
||||||
<p class="text-xs text-amber-600 mt-1">⚠ Container restart needed after env var changes.</p>
|
<p class="text-xs text-amber-600 mt-1">⚠ Container restart needed after env var changes.</p>
|
||||||
</td>
|
</td>
|
||||||
</template>
|
</template>
|
||||||
@@ -175,17 +146,14 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="flex flex-col sm:flex-row gap-2 mb-3">
|
<form @submit.prevent="addVar($refs.newKey, $refs.newVal)" class="flex flex-col sm:flex-row gap-2">
|
||||||
<input type="text" x-model="newKey" placeholder="KEY" class="input flex-1 font-mono text-sm">
|
<input x-ref="newKey" type="text" placeholder="KEY" required class="input flex-1 font-mono text-sm">
|
||||||
<input type="text" x-model="newValue" placeholder="value" class="input flex-1 font-mono text-sm">
|
<input x-ref="newVal" type="text" placeholder="value" required class="input flex-1 font-mono text-sm">
|
||||||
<button type="button" @click="addVar()" class="btn-secondary">Add</button>
|
<button type="submit" class="btn-primary">Add</button>
|
||||||
</div>
|
</form>
|
||||||
<textarea name="env_vars" x-ref="envVarsField" class="hidden">{{range .EnvVars}}{{.Key}}={{.Value}}
|
<form x-ref="bulkForm" method="POST" action="/apps/{{.App.ID}}/env" class="hidden">
|
||||||
{{end}}</textarea>
|
{{ .CSRFField }}
|
||||||
<div class="flex items-center gap-3">
|
<textarea x-ref="bulkData" name="env_vars"></textarea>
|
||||||
<button type="submit" class="btn-primary">Save All</button>
|
|
||||||
<p class="text-xs text-amber-600">⚠ Container restart needed after env var changes.</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user