Compare commits
1 Commits
30f81078bd
...
bb07622e62
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb07622e62 |
@@ -732,11 +732,11 @@ func TestHandleEnvVarDeleteUsesCorrectRouteParam(t *testing.T) {
|
|||||||
|
|
||||||
// Use chi router with the real route pattern to test param name
|
// Use chi router with the real route pattern to test param name
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
r.Post("/apps/{id}/env/{varID}/delete", testCtx.handlers.HandleEnvVarDelete())
|
r.Post("/apps/{id}/env-vars/{varID}/delete", testCtx.handlers.HandleEnvVarDelete())
|
||||||
|
|
||||||
request := httptest.NewRequest(
|
request := httptest.NewRequest(
|
||||||
http.MethodPost,
|
http.MethodPost,
|
||||||
"/apps/"+createdApp.ID+"/env/"+strconv.FormatInt(envVar.ID, 10)+"/delete",
|
"/apps/"+createdApp.ID+"/env-vars/"+strconv.FormatInt(envVar.ID, 10)+"/delete",
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
|
|||||||
@@ -82,9 +82,9 @@ func (s *Server) SetupRoutes() {
|
|||||||
r.Post("/apps/{id}/start", s.handlers.HandleAppStart())
|
r.Post("/apps/{id}/start", s.handlers.HandleAppStart())
|
||||||
|
|
||||||
// Environment variables
|
// Environment variables
|
||||||
r.Post("/apps/{id}/env", s.handlers.HandleEnvVarAdd())
|
r.Post("/apps/{id}/env-vars", s.handlers.HandleEnvVarAdd())
|
||||||
r.Post("/apps/{id}/env/{varID}/edit", s.handlers.HandleEnvVarEdit())
|
r.Post("/apps/{id}/env-vars/{varID}/edit", s.handlers.HandleEnvVarEdit())
|
||||||
r.Post("/apps/{id}/env/{varID}/delete", s.handlers.HandleEnvVarDelete())
|
r.Post("/apps/{id}/env-vars/{varID}/delete", s.handlers.HandleEnvVarDelete())
|
||||||
|
|
||||||
// Labels
|
// Labels
|
||||||
r.Post("/apps/{id}/labels", s.handlers.HandleLabelAdd())
|
r.Post("/apps/{id}/labels", s.handlers.HandleLabelAdd())
|
||||||
|
|||||||
@@ -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
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
<template x-if="!editing">
|
<template x-if="!editing">
|
||||||
<td class="text-right">
|
<td class="text-right">
|
||||||
<button @click="editing = true" class="text-primary-600 hover:text-primary-800 text-sm mr-2">Edit</button>
|
<button @click="editing = true" class="text-primary-600 hover:text-primary-800 text-sm mr-2">Edit</button>
|
||||||
<form method="POST" action="/apps/{{$.App.ID}}/env/{{.ID}}/delete" class="inline" x-data="confirmAction('Delete this environment variable?')" @submit="confirm($event)">
|
<form method="POST" action="/apps/{{$.App.ID}}/env-vars/{{.ID}}/delete" class="inline" x-data="confirmAction('Delete this environment variable?')" @submit="confirm($event)">
|
||||||
{{ $.CSRFField }}
|
{{ $.CSRFField }}
|
||||||
<button type="submit" class="text-error-500 hover:text-error-700 text-sm">Delete</button>
|
<button type="submit" class="text-error-500 hover:text-error-700 text-sm">Delete</button>
|
||||||
</form>
|
</form>
|
||||||
@@ -130,7 +130,7 @@
|
|||||||
</template>
|
</template>
|
||||||
<template x-if="editing">
|
<template x-if="editing">
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<form method="POST" action="/apps/{{$.App.ID}}/env/{{.ID}}/edit" class="flex gap-2 items-center">
|
<form method="POST" action="/apps/{{$.App.ID}}/env-vars/{{.ID}}/edit" class="flex gap-2 items-center">
|
||||||
{{ $.CSRFField }}
|
{{ $.CSRFField }}
|
||||||
<input type="text" name="key" value="{{.Key}}" required class="input flex-1 font-mono text-sm">
|
<input type="text" name="key" value="{{.Key}}" required class="input flex-1 font-mono text-sm">
|
||||||
<input type="text" name="value" value="{{.Value}}" required class="input flex-1 font-mono text-sm">
|
<input type="text" name="value" value="{{.Value}}" required class="input flex-1 font-mono text-sm">
|
||||||
@@ -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