feat: post-deploy info card with app link, first steps, and credentials

After successful deploy, shows a rich info card instead of auto-redirecting
to the apps list. Includes direct app link, first steps from catalog metadata,
default credentials info, and link to settings page for password reveal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 15:27:33 +01:00
parent e737704e68
commit a30f4c0234
2 changed files with 68 additions and 8 deletions
+5
View File
@@ -1,5 +1,10 @@
## Changelog
### v0.28.5 — Post-Deploy Info Card (2026-02-23)
#### Added
- **Post-deploy success page** (`web/templates/deploy.html`) — After a successful deploy, instead of auto-redirecting to the apps list, shows a rich info card with: direct app link ("Alkalmazás megnyitása ↗"), first steps from catalog metadata (with DOMAIN placeholders replaced), default credentials info, documentation link, and a link to the settings page where passwords can be revealed. Also shown for unhealthy/timeout states since apps may still be usable during initialization.
### v0.28.4 — Telemetry: Skip Stopped Apps (2026-02-23)
#### Fixed
+63 -8
View File
@@ -393,6 +393,61 @@
</div>
<script>
var postDeployInfo = {
firstSteps: {{json .Meta.AppInfo.FirstSteps}},
defaultCreds: {{json .Meta.AppInfo.DefaultCreds}},
docsURL: {{json .Meta.AppInfo.DocsURL}},
domain: {{json .Domain}},
displayName: {{json .Meta.DisplayName}}
};
function buildPostDeployCard(stackName) {
var subdomain = '';
var sdField = document.getElementById('field-SUBDOMAIN');
if (sdField) subdomain = sdField.value.trim();
if (!subdomain && '{{.Meta.Subdomain}}') subdomain = '{{.Meta.Subdomain}}';
var html = '';
// App link
if (subdomain && postDeployInfo.domain) {
var appURL = 'https://' + subdomain + '.' + postDeployInfo.domain;
html += '<div style="text-align:center;margin:1.5rem 0">' +
'<a href="' + appURL + '" target="_blank" class="btn btn-primary btn-lg">Alkalmazás megnyitása ↗</a>' +
'</div>';
}
// First steps
if (postDeployInfo.firstSteps && postDeployInfo.firstSteps.length > 0) {
html += '<div class="app-info-card" style="margin-top:1rem"><h4>Első lépések</h4><ol class="app-info-list">';
for (var i = 0; i < postDeployInfo.firstSteps.length; i++) {
var step = postDeployInfo.firstSteps[i].replace(/DOMAIN/g, postDeployInfo.domain);
html += '<li>' + step + '</li>';
}
html += '</ol></div>';
}
// Default creds
if (postDeployInfo.defaultCreds) {
var creds = postDeployInfo.defaultCreds.replace(/DOMAIN/g, postDeployInfo.domain);
html += '<div class="app-info-card" style="margin-top:1rem"><h4>Bejelentkezés</h4>' +
'<p class="app-info-creds">' + creds + '</p></div>';
}
// Docs link
if (postDeployInfo.docsURL) {
html += '<div style="margin-top:1rem"><a href="' + postDeployInfo.docsURL + '" target="_blank" class="btn btn-sm btn-outline">Dokumentáció ↗</a></div>';
}
// Action buttons
html += '<div style="display:flex;gap:.75rem;margin-top:1.5rem;flex-wrap:wrap">' +
'<a href="/stacks/' + stackName + '/deploy" class="btn btn-outline">Beállítások és jelszavak megtekintése</a>' +
'<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>' +
'</div>';
return html;
}
function toggleCrossDriveFields() {
var enabled = document.getElementById('cross-drive-enabled').checked;
var fields = document.querySelectorAll('.cross-drive-field');
@@ -635,7 +690,7 @@ document.getElementById('deploy-form').addEventListener('submit', async function
setStep(stepHealth, 'warn', 'Időtúllépés — az alkalmazás még indulhat');
resultEl.innerHTML = '<div class="alert alert-warning" style="margin-top:1rem">' +
'A telepítés időtúllépésbe futott. Az alkalmazás még indulhat.' +
'</div><a href="/stacks" class="btn btn-primary" style="margin-top:.75rem">Alkalmazások megtekintése</a>';
'</div>' + buildPostDeployCard(stackName);
resultEl.style.display = 'block';
return;
}
@@ -667,21 +722,21 @@ document.getElementById('deploy-form').addEventListener('submit', async function
setStep(stepHealth, 'done', 'Alkalmazás kész!');
progressEl.querySelector('h3').textContent = 'Telepítés sikeres!';
resultEl.innerHTML = '<div class="alert alert-info" style="margin-top:1rem">' +
'Az alkalmazás fut. Átirányítás 3 másodperc múlva...' +
'</div>';
'Az alkalmazás sikeresen telepítve és fut!' +
'</div>' + buildPostDeployCard(stackName);
resultEl.style.display = 'block';
setTimeout(function() { window.location.href = '/stacks'; }, 3000);
} else if (state === 'starting') {
setStep(stepContainers, 'done', 'Konténerek elindultak');
setStep(stepHealth, 'active', 'Alkalmazás inicializálása...');
} else if (state === 'unhealthy') {
clearInterval(pollTimer);
setStep(stepContainers, 'done', 'Konténerek elindultak');
setStep(stepHealth, 'warn', 'Állapotjelző: nem egészséges');
setStep(stepHealth, 'warn', 'Állapotjelző: még inicializál');
progressEl.querySelector('h3').textContent = 'Telepítés sikeres!';
resultEl.innerHTML = '<div class="alert alert-warning" style="margin-top:1rem">' +
'Az alkalmazás elindult, de az állapotjelző nem egészséges. ' +
'Ez normális lehet az első percekben.' +
'</div><a href="/stacks" class="btn btn-primary" style="margin-top:.75rem">Alkalmazások megtekintése</a>';
'Az alkalmazás elindult, de még inicializálódik. ' +
'Ez normális az első percekben — az alkalmazás már elérhető lehet.' +
'</div>' + buildPostDeployCard(stackName);
resultEl.style.display = 'block';
} else if (state === 'exited' || state === 'stopped') {
clearInterval(pollTimer);