fix: match original table UI with immediate per-action submission
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:
clawbot
2026-03-10 11:23:36 -07:00
parent 690b7d4590
commit 3f96f4f81b
2 changed files with 113 additions and 83 deletions

View File

@@ -6,6 +6,68 @@
*/
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) => ({
appId: config.appId,
currentDeploymentId: config.initialDeploymentId,