diff --git a/frontend/index.html b/frontend/index.html
index 1c1516d..bdffb1a 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -475,6 +475,7 @@ function renderAppCard(app) {
+
@@ -505,6 +506,12 @@ function renderAppCard(app) {
Click "Refresh" to load backups.
+
+
+
+
+
Click "Load App Variables" to view routing details & directories.
+
`;
@@ -562,6 +569,7 @@ function attachCardListeners() {
document.getElementById(`tab-compose-${app}`).classList.toggle("active", tabName === "compose");
document.getElementById(`tab-logs-${app}`).classList.toggle("active", tabName === "logs");
document.getElementById(`tab-backups-${app}`).classList.toggle("active", tabName === "backups");
+ document.getElementById(`tab-routing-${app}`).classList.toggle("active", tabName === "routing");
if (tabName === "logs") loadLogs(app);
if (tabName === "backups") loadBackups(app);
@@ -572,13 +580,22 @@ function attachCardListeners() {
document.querySelectorAll("[data-action]").forEach(btn => {
btn.onclick = (e) => {
e.stopPropagation();
- handleAction(btn.dataset.action, btn.dataset.app);
+ handleAction(btn.dataset.action, btn.dataset.app, btn);
};
});
}
// ─── Actions ───
-async function handleAction(action, name) {
+async function handleAction(action, name, btnElement) {
+ const originalWidth = btnElement ? btnElement.offsetWidth : null;
+ const originalHtml = btnElement ? btnElement.innerHTML : null;
+
+ if (btnElement) {
+ btnElement.disabled = true;
+ if (originalWidth) btnElement.style.width = `${originalWidth}px`;
+ btnElement.innerHTML = ``;
+ }
+
try {
switch (action) {
case "deploy":
@@ -628,9 +645,34 @@ async function handleAction(action, name) {
setStatus(`Backing up ${name}...`);
await api.backup(name);
setStatus(`Backup created for ${name}.`);
- await loadBackups(name);
+ case "fetch-routing":
+ setStatus(`Fetching routing details for ${name}...`);
+ const appRes = await api.getApp(name);
+ if (appRes.app) {
+ const info = appRes.app;
+ document.getElementById(`routing-${name}`).innerHTML = `
+Primary Domain: ${escHtml(info.APP_DOMAIN || "-")}
+All Domains: ${escHtml(info.APP_DOMAINS || "-")}
+Upstream Target: ${escHtml(info.APP_UPSTREAM || "-")}
+Protected: ${escHtml(info.APP_AUTH_PROTECTED || "-")}
+Port: ${escHtml(info.APP_PORT || "-")}
+
+Volumes Directory: ${escHtml(info.APP_VOLUME_DIR || "-")}
+Stack Directory: ${escHtml(info.APP_STACK_DIR || "-")}
+Caddy Route Block: ${escHtml(info.APP_ROUTE_FILE || "-")}
+ `.trim();
+ }
+ setStatus(`Loaded routing for ${name}.`);
break;
- case "refresh-backups":
+ }
+ } catch (err) {
+ setStatus(`${action} failed for ${name}: ${err.message}`, true);
+ } finally {
+ if (btnElement && originalHtml) {
+ btnElement.disabled = false;
+ btnElement.innerHTML = originalHtml;
+ btnElement.style.width = '';
+ }
await loadBackups(name);
break;
case "render-route":
diff --git a/panelctl.sh b/panelctl.sh
index 050cd7b..9334356 100644
--- a/panelctl.sh
+++ b/panelctl.sh
@@ -344,7 +344,7 @@ cmd_deploy() {
cmd_render_route "${name}"
- if ! run_compose -f "${APP_COMPOSE_FILE}" up -d 2>&1 | systemd-cat -t panelctl -p info 2>/dev/null; then
+ if ! run_compose -f "${APP_COMPOSE_FILE}" up -d --remove-orphans 2>&1 | systemd-cat -t panelctl -p info 2>/dev/null; then
log err "Deployment failed for app '${name}'"
fail "compose up failed"
fi
@@ -359,9 +359,9 @@ cmd_restart() {
log info "Restarting app '${name}'"
- run_compose -f "${APP_COMPOSE_FILE}" down || fail "compose down failed"
+ run_compose -f "${APP_COMPOSE_FILE}" down --remove-orphans || fail "compose down failed"
- if ! run_compose -f "${APP_COMPOSE_FILE}" up -d 2>&1; then
+ if ! run_compose -f "${APP_COMPOSE_FILE}" up -d --remove-orphans 2>&1; then
fail "compose up failed during restart"
fi
@@ -373,7 +373,7 @@ cmd_stop() {
validate_name "${name}"
load_app "${name}"
- run_compose -f "${APP_COMPOSE_FILE}" down || fail "compose down failed"
+ run_compose -f "${APP_COMPOSE_FILE}" down --remove-orphans || fail "compose down failed"
log info "stopped app '${name}'"
}
@@ -429,7 +429,7 @@ cmd_remove() {
validate_name "${name}"
load_app "${name}"
- run_compose -f "${APP_COMPOSE_FILE}" down 2>/dev/null || true
+ run_compose -f "${APP_COMPOSE_FILE}" down --remove-orphans 2>/dev/null || true
# Remove this app's block from the aggregate routes file.
local route_file