fix: correct env var add form action path
All checks were successful
Check / check (pull_request) Successful in 5s
All checks were successful
Check / check (pull_request) Successful in 5s
The 'Add' environment variable form in app_detail.html was posting to
/apps/{id}/env, but the route is registered as /apps/{id}/env-vars.
This path mismatch caused a 404 when trying to add environment variables
from the app detail page.
Fix the form action to use the correct /apps/{id}/env-vars path,
matching the route defined in routes.go.
closes #156
This commit is contained in:
@@ -31,14 +31,22 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
// Set up scroll listeners after DOM is ready
|
// Set up scroll listeners after DOM is ready
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this._initScrollTracking(this.$refs.containerLogsWrapper, '_containerAutoScroll');
|
this._initScrollTracking(
|
||||||
this._initScrollTracking(this.$refs.buildLogsWrapper, '_buildAutoScroll');
|
this.$refs.containerLogsWrapper,
|
||||||
|
"_containerAutoScroll",
|
||||||
|
);
|
||||||
|
this._initScrollTracking(
|
||||||
|
this.$refs.buildLogsWrapper,
|
||||||
|
"_buildAutoScroll",
|
||||||
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
_schedulePoll() {
|
_schedulePoll() {
|
||||||
if (this._pollTimer) clearTimeout(this._pollTimer);
|
if (this._pollTimer) clearTimeout(this._pollTimer);
|
||||||
const interval = Alpine.store("utils").isDeploying(this.appStatus) ? 1000 : 10000;
|
const interval = Alpine.store("utils").isDeploying(this.appStatus)
|
||||||
|
? 1000
|
||||||
|
: 10000;
|
||||||
this._pollTimer = setTimeout(() => {
|
this._pollTimer = setTimeout(() => {
|
||||||
this.fetchAll();
|
this.fetchAll();
|
||||||
this._schedulePoll();
|
this._schedulePoll();
|
||||||
@@ -47,18 +55,29 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
_initScrollTracking(el, flag) {
|
_initScrollTracking(el, flag) {
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.addEventListener('scroll', () => {
|
el.addEventListener(
|
||||||
|
"scroll",
|
||||||
|
() => {
|
||||||
this[flag] = Alpine.store("utils").isScrolledToBottom(el);
|
this[flag] = Alpine.store("utils").isScrolledToBottom(el);
|
||||||
}, { passive: true });
|
},
|
||||||
|
{ passive: true },
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
fetchAll() {
|
fetchAll() {
|
||||||
this.fetchAppStatus();
|
this.fetchAppStatus();
|
||||||
// Only fetch logs when the respective pane is visible
|
// Only fetch logs when the respective pane is visible
|
||||||
if (this.$refs.containerLogsWrapper && this._isElementVisible(this.$refs.containerLogsWrapper)) {
|
if (
|
||||||
|
this.$refs.containerLogsWrapper &&
|
||||||
|
this._isElementVisible(this.$refs.containerLogsWrapper)
|
||||||
|
) {
|
||||||
this.fetchContainerLogs();
|
this.fetchContainerLogs();
|
||||||
}
|
}
|
||||||
if (this.showBuildLogs && this.$refs.buildLogsWrapper && this._isElementVisible(this.$refs.buildLogsWrapper)) {
|
if (
|
||||||
|
this.showBuildLogs &&
|
||||||
|
this.$refs.buildLogsWrapper &&
|
||||||
|
this._isElementVisible(this.$refs.buildLogsWrapper)
|
||||||
|
) {
|
||||||
this.fetchBuildLogs();
|
this.fetchBuildLogs();
|
||||||
}
|
}
|
||||||
this.fetchRecentDeployments();
|
this.fetchRecentDeployments();
|
||||||
@@ -107,7 +126,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.containerStatus = data.status;
|
this.containerStatus = data.status;
|
||||||
if (changed && this._containerAutoScroll) {
|
if (changed && this._containerAutoScroll) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
Alpine.store("utils").scrollToBottom(this.$refs.containerLogsWrapper);
|
Alpine.store("utils").scrollToBottom(
|
||||||
|
this.$refs.containerLogsWrapper,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -128,7 +149,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.buildStatus = data.status;
|
this.buildStatus = data.status;
|
||||||
if (changed && this._buildAutoScroll) {
|
if (changed && this._buildAutoScroll) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
Alpine.store("utils").scrollToBottom(this.$refs.buildLogsWrapper);
|
Alpine.store("utils").scrollToBottom(
|
||||||
|
this.$refs.buildLogsWrapper,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -138,7 +161,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
async fetchRecentDeployments() {
|
async fetchRecentDeployments() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/apps/${this.appId}/recent-deployments`);
|
const res = await fetch(
|
||||||
|
`/apps/${this.appId}/recent-deployments`,
|
||||||
|
);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
this.deployments = data.deployments || [];
|
this.deployments = data.deployments || [];
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -171,7 +196,8 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
get buildStatusBadgeClass() {
|
get buildStatusBadgeClass() {
|
||||||
return (
|
return (
|
||||||
Alpine.store("utils").statusBadgeClass(this.buildStatus) + " text-xs"
|
Alpine.store("utils").statusBadgeClass(this.buildStatus) +
|
||||||
|
" text-xs"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,8 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.$el.querySelectorAll("[data-time]").forEach((el) => {
|
this.$el.querySelectorAll("[data-time]").forEach((el) => {
|
||||||
const time = el.getAttribute("data-time");
|
const time = el.getAttribute("data-time");
|
||||||
if (time) {
|
if (time) {
|
||||||
el.textContent = Alpine.store("utils").formatRelativeTime(time);
|
el.textContent =
|
||||||
|
Alpine.store("utils").formatRelativeTime(time);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 60000);
|
}, 60000);
|
||||||
|
|||||||
@@ -26,9 +26,16 @@ document.addEventListener("alpine:init", () => {
|
|||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const wrapper = this.$refs.logsWrapper;
|
const wrapper = this.$refs.logsWrapper;
|
||||||
if (wrapper) {
|
if (wrapper) {
|
||||||
wrapper.addEventListener('scroll', () => {
|
wrapper.addEventListener(
|
||||||
this._autoScroll = Alpine.store("utils").isScrolledToBottom(wrapper);
|
"scroll",
|
||||||
}, { passive: true });
|
() => {
|
||||||
|
this._autoScroll =
|
||||||
|
Alpine.store("utils").isScrolledToBottom(
|
||||||
|
wrapper,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
{ passive: true },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,7 +66,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
// Scroll to bottom only when content changes AND user hasn't scrolled up
|
// Scroll to bottom only when content changes AND user hasn't scrolled up
|
||||||
if (logsChanged && this._autoScroll) {
|
if (logsChanged && this._autoScroll) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
Alpine.store("utils").scrollToBottom(this.$refs.logsWrapper);
|
Alpine.store("utils").scrollToBottom(
|
||||||
|
this.$refs.logsWrapper,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
|
|
||||||
if (diffSec < 60) return "just now";
|
if (diffSec < 60) return "just now";
|
||||||
if (diffMin < 60)
|
if (diffMin < 60)
|
||||||
return diffMin + (diffMin === 1 ? " minute ago" : " minutes ago");
|
return (
|
||||||
|
diffMin + (diffMin === 1 ? " minute ago" : " minutes ago")
|
||||||
|
);
|
||||||
if (diffHour < 24)
|
if (diffHour < 24)
|
||||||
return diffHour + (diffHour === 1 ? " hour ago" : " hours ago");
|
return diffHour + (diffHour === 1 ? " hour ago" : " hours ago");
|
||||||
if (diffDay < 7)
|
if (diffDay < 7)
|
||||||
@@ -33,7 +35,8 @@ document.addEventListener("alpine:init", () => {
|
|||||||
* Get the badge class for a given status
|
* Get the badge class for a given status
|
||||||
*/
|
*/
|
||||||
statusBadgeClass(status) {
|
statusBadgeClass(status) {
|
||||||
if (status === "running" || status === "success") return "badge-success";
|
if (status === "running" || status === "success")
|
||||||
|
return "badge-success";
|
||||||
if (status === "building" || status === "deploying")
|
if (status === "building" || status === "deploying")
|
||||||
return "badge-warning";
|
return "badge-warning";
|
||||||
if (status === "failed" || status === "error") return "badge-error";
|
if (status === "failed" || status === "error") return "badge-error";
|
||||||
@@ -72,7 +75,9 @@ document.addEventListener("alpine:init", () => {
|
|||||||
*/
|
*/
|
||||||
isScrolledToBottom(el, tolerance = 30) {
|
isScrolledToBottom(el, tolerance = 30) {
|
||||||
if (!el) return true;
|
if (!el) return true;
|
||||||
return el.scrollHeight - el.scrollTop - el.clientHeight <= tolerance;
|
return (
|
||||||
|
el.scrollHeight - el.scrollTop - el.clientHeight <= tolerance
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -146,7 +146,7 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
<form method="POST" action="/apps/{{.App.ID}}/env" class="flex flex-col sm:flex-row gap-2">
|
<form method="POST" action="/apps/{{.App.ID}}/env-vars" class="flex flex-col sm:flex-row gap-2">
|
||||||
{{ .CSRFField }}
|
{{ .CSRFField }}
|
||||||
<input type="text" name="key" placeholder="KEY" required class="input flex-1 font-mono text-sm">
|
<input type="text" name="key" placeholder="KEY" required class="input flex-1 font-mono text-sm">
|
||||||
<input type="text" name="value" placeholder="value" required class="input flex-1 font-mono text-sm">
|
<input type="text" name="value" placeholder="value" required class="input flex-1 font-mono text-sm">
|
||||||
|
|||||||
Reference in New Issue
Block a user