fe0228c877
gates / gates (push) Failing after 46s
FOUND IN A BROWSER, and findable no other way. Every endpoint-level marker passed and the page was
unusable: the tab bar rendered as four bare underlined links run together, and the app logo filled a
third of the screen. The shell had been written with new class names — app-hero, app-hero-logo,
app-tabs — and only the first two of those had any CSS at all, under the names the design system
already uses.
- the shell now reuses .app-info-hero / .app-info-logo / .app-info-hero-text, which carry the
80px logo box, the hairline border and the 2px radius. Inventing a parallel name is what left
the logo unsized; REUSE.md's first rule, learned again.
- .app-tabs / .app-tab / .app-tab-active are genuinely new and are now styled to design system
v2: 2px radius, hairline --line border, no shadow, --text-2 for an inactive tab and
--blue-bright only for the one the household is on (blue marks where you are, not a deviation),
the active tab sitting on the bar's own line, and a narrower gutter under 640px.
- .after-start-card gets a blue left hairline — it is a request, not a fault, so no amber.
- .app-faq gets its dt/dd rules.
TestAppPage_DeployedDefaultsToSettings sliced the header at `class="app-hero"`; it now slices at the
reused class and fails loudly if either marker is gone, instead of slicing on -1. The 36 app-page
parity fixtures are recaptured — and no other fixture moved, which is the evidence that the shell
change reaches only the app page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
138 lines
8.2 KiB
HTML
138 lines
8.2 KiB
HTML
{{/* THE APP PAGE'S SHARED SHELL (v0.315.0).
|
|
|
|
One header and one set of banners for all four tabs, so a held app says it is held on the log
|
|
tab too and the household never has to go back to the details page to find out why.
|
|
|
|
Order is load-bearing and is the alert-placement rule (R-571), unchanged: the page-level
|
|
banners sit ABOVE the tab bar, because they are about the APP, not about the tab. An inline
|
|
alert belongs inside the tab that owns it.
|
|
|
|
The header carries NO Start/Restart/Stop. They used to sit here and read as menu buttons; they
|
|
are now the first card of the „Beallitasok" tab, where a state change belongs. */}}
|
|
|
|
{{define "app_shell_start"}}
|
|
<div class="page-header">
|
|
<div style="display:flex;align-items:center;gap:1rem">
|
|
<a href="/stacks" class="btn btn-sm btn-outline">{{T "app_info.alkalmazasok"}}</a>
|
|
<h2>{{.Meta.DisplayName}}</h2>
|
|
</div>
|
|
<div style="display:flex;align-items:center;gap:.5rem">
|
|
{{if .Deployed}}
|
|
<span class="stack-state-badge state-{{stateColor .Stack.State}}">{{stateLabel .Stack.State}}</span>
|
|
{{if .Stack.Orphaned}}<span class="badge badge-orphaned">{{T "common.elavult"}}</span>{{end}}
|
|
{{template "meta_badge" (lifecycleBadge .Meta)}}
|
|
{{template "meta_badge" (updateBadge .Stack)}}{{if and (gt .Stack.LadderStepsLeft 0) (not .Stack.HoldReason)}}<small class="text-muted" data-ladder-steps="{{.Stack.LadderStepsLeft}}">{{T "app_info.ladder_steps_left"}} {{.Stack.LadderStepsLeft}}</small>{{end}}
|
|
{{if and .AppOperational .EffectiveSubdomain}}<a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-sm btn-primary" data-app-open="1">{{T "app_info.megnyitas"}}</a>{{end}}
|
|
{{if .Stack.Orphaned}}<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Stack.Name}}')">{{T "common.torles"}}</button>{{end}}
|
|
{{end}}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="app-info-hero">
|
|
<img class="app-info-logo" src="{{logoURL .Meta.Slug}}"
|
|
alt="{{.Meta.DisplayName}}" data-fallback="/static/app-placeholder.svg"
|
|
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='{{logoPNGURL .Meta.Slug}}';}else if(this.dataset.fallback&&this.dataset.step==='1'){this.dataset.step='2';this.src=this.dataset.fallback;}else{this.onerror=null;this.style.visibility='hidden';}">
|
|
<div class="app-info-hero-text">
|
|
{{if .AppInfo.Tagline}}<p class="app-info-tagline">{{.AppInfo.Tagline}}</p>{{else}}<p class="app-info-tagline">{{.Meta.Description}}</p>{{end}}
|
|
<div class="stack-meta-badges">
|
|
<span class="meta-badge">~{{.Meta.Resources.MemRequest}} RAM</span>
|
|
<span class="meta-badge">{{.Meta.Category}}</span>
|
|
{{if .Meta.Resources.NeedsHDD}}<span class="meta-badge meta-badge-warn">{{T "common.hdd_szukseges"}}</span>{{end}}
|
|
{{if .Meta.Resources.HungarianUI}}<span class="meta-badge meta-badge-ok">{{T "common.magyar_felulet"}}</span>{{end}}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{template "app_banners" .}}
|
|
{{template "app_tab_bar" .}}
|
|
{{end}}
|
|
|
|
{{/* app_banners — every page-level alert the app can carry, on EVERY tab. Each keeps the marker it
|
|
had on the details page, so the tests and the operator's greps still find it. */}}
|
|
{{define "app_banners"}}
|
|
{{- if .Meta.IsAbandoned}}
|
|
<div class="alert alert-warning" style="margin-top:.75rem">{{T "app_info.az_alkalmazas_fejlesztoje_felhagyott_a"}}</div>
|
|
{{- end}}
|
|
{{- if .LostFiles}}
|
|
<div class="app-info-card alert-warning" style="margin-top:1rem" id="lost-files-card">
|
|
<h3>{{T "app_info.lost_files_title"}}</h3>
|
|
<p>{{T "app_info.lost_files_text"}}{{if .LostBootAt}} <strong data-lost-boot-at>{{.LostBootAt}}</strong>{{end}}</p>
|
|
<ul data-lost-files>
|
|
{{- range .LostFiles}}
|
|
<li><span class="mono">{{.Name}}</span>{{if .Taken}} — {{T "app_info.lost_files_taken"}} {{.Taken}}{{end}}</li>
|
|
{{- end}}
|
|
</ul>
|
|
<p>{{T "app_info.lost_files_hint"}}</p>
|
|
<button type="button" class="btn btn-sm btn-outline" onclick="appPress(this, '/apps/{{.Meta.Slug}}/lost-files/dismiss')">{{T "app_info.lost_files_dismiss_btn"}}</button>
|
|
<span class="form-hint app-press-err" style="display:none;color:var(--red)"></span>
|
|
</div>
|
|
{{- end}}
|
|
{{if .Stack.Updating}}
|
|
<div class="alert alert-info" style="margin-top:1rem" data-update-phase="{{.Stack.UpdatePhase}}">{{updatePhaseText .Stack}}</div>
|
|
{{else if and .Stack.HoldReason (eq .Stack.HoldKind "unhealthy_stop")}}
|
|
<div class="alert alert-warning" style="margin-top:1rem" data-unhealthy-stop="true">{{holdText .Stack}}</div>
|
|
{{else if .Stack.HoldReason}}
|
|
<div class="alert alert-error" style="margin-top:1rem" data-held="true">{{holdText .Stack}}{{if not .Stack.HoldNoWholeCopy}} <a href="/backups/apps" class="btn btn-sm btn-outline">{{T "app_info.mentesek"}}</a>{{end}}{{if .HoldLogKept}}<br><span data-hold-log="true">{{T "app_info.hold_log_kept"}}</span> <a href="/apps/{{.Meta.Slug}}/logs" class="btn btn-sm btn-outline">{{T "app_info.naplo"}}</a>{{end}}</div>
|
|
{{else if .Stack.UpdateError}}
|
|
<div class="alert alert-warning" style="margin-top:1rem" data-update-error="true">{{updateErrorText .Stack}}</div>
|
|
{{else if .UpdateUndoneLine}}
|
|
<div class="alert alert-info" style="margin-top:1rem" data-update-undone="true">{{.UpdateUndoneLine}}</div>
|
|
{{else if .AutoUpdateLine}}
|
|
<div class="alert alert-info" style="margin-top:1rem" data-auto-update="done">{{.AutoUpdateLine}}</div>
|
|
{{end}}{{if and .Stack.HealthProbe .Stack.HealthProbe.NotChecked}}{{/* R-772: the declared health check could not run — said, never shown as healthy. */}}
|
|
<p class="form-hint" style="margin-top:0.5rem" data-health-not-checked="true">{{T "health.no_probe_container"}}</p>{{end}}
|
|
{{if .MissingStorageLabel}}
|
|
<div class="alert alert-warning" style="margin-top:1rem">
|
|
<strong><svg class="ico ico-sm"><use href="#i-triangle-alert"/></svg> {{T "app_info.hianyzo_tarhely"}} {{.MissingStorageLabel}}</strong><br>
|
|
{{T "app_info.ennek_az_alkalmazasnak_az_adattaroloja"}}
|
|
</div>
|
|
{{end}}
|
|
{{end}}
|
|
|
|
{{/* app_tab_bar — plain links, no JavaScript. An alert's link must land ON a tab, and the log
|
|
poller must not run on the other three; hidden panels give neither. */}}
|
|
{{define "app_tab_bar"}}
|
|
{{- if .AppTabs}}
|
|
<nav class="app-tabs" aria-label="{{T "app_page.tabs_label"}}">
|
|
{{- range .AppTabs}}
|
|
<a class="app-tab{{if .Active}} app-tab-active{{end}}" href="{{.Href}}" data-tab="{{.Key}}"
|
|
{{if .Active}}aria-current="page" aria-selected="true"{{else}}aria-selected="false"{{end}}>{{.Label}}</a>
|
|
{{- end}}
|
|
</nav>
|
|
{{- end}}
|
|
{{end}}
|
|
|
|
{{/* app_press_script — the shared POST-and-reload used by every card button on the page. It was the
|
|
details page's own script; the cards moved to tabs and it came with them. */}}
|
|
{{define "app_press_script"}}
|
|
<script>
|
|
function appPress(btn, url) {
|
|
var err = btn.parentNode.querySelector('.app-press-err');
|
|
if (err) { err.style.display = 'none'; }
|
|
btn.disabled = true;
|
|
fetch(url, {method: 'POST', headers: csrfHeaders(), credentials: 'same-origin'})
|
|
.then(function (r) { return r.json(); })
|
|
.then(function (j) {
|
|
if (!j.ok) { throw new Error(j.error || ''); }
|
|
window.location.reload();
|
|
})
|
|
.catch(function (e) { btn.disabled = false; if (err) { err.textContent = e.message; err.style.display = 'inline'; } });
|
|
}
|
|
// v0.315.0: one script for EVERY after-start field (Part 4). The env var is the only thing that
|
|
// varies, and the server decides what the value means — see after_start.go.
|
|
function appAfterStart(btn, slug, envVar) {
|
|
var box = btn.closest('.after-start-card'), err = box.querySelector('.app-press-err');
|
|
var input = box.querySelector('input');
|
|
err.style.display = 'none'; btn.disabled = true;
|
|
fetch('/apps/' + slug + '/after-start/' + envVar, {
|
|
method: 'POST',
|
|
headers: Object.assign({'Content-Type': 'application/json'}, csrfHeaders()),
|
|
credentials: 'same-origin',
|
|
body: JSON.stringify({value: (input ? input.value : '').trim()})
|
|
}).then(function (r) { return r.json(); })
|
|
.then(function (j) { if (!j.ok) { throw new Error(j.error || ''); } window.location.reload(); })
|
|
.catch(function (e) { btn.disabled = false; err.textContent = e.message; err.style.display = 'inline'; });
|
|
}
|
|
</script>
|
|
{{end}}
|