v0.315.0 worked and looked, in the operator's words, messy: boxes of different widths stacked down
the page, with the same card shape around things you act on and things you only read. The mockup
approved on 2026-10-15 replaces it, and each of its three rules has a reason:
ONE COLUMN OF ONE WIDTH (820px). Differing widths read as a page assembled by accident.
SECTIONS ARE HAIRLINES, NOT CARDS. A card says "this is a thing you act on"; most of an app page
is a thing you READ, so a card there is a promise the content cannot keep.
A BOX MEANS SOMETHING. Exactly two remain: blue when the app is waiting for the household,
amber when something is risky. The exception-colour principle applied to shape as well.
What moved:
- the hero loses its box: a 64px logo tile, the name at 26px, the tagline, then hairline chips
(state, lifecycle, update) and meta text (the address, the last automatic update);
- the tabs are links with a 2px blue underline under the open one — no pills, no fills;
- sections are `padding: 28px 0` with a bottom hairline; rows are a 200px label column so every
value on the page starts at the same x, and stack under 720px;
- the after-start callout is now the FIRST thing on Beallitasok. A household opening a Plex that
wants its claim code should meet the code box, not a Restart button;
- "2. mentes beallitasa — <app>" and "<app> — Exportalas" are gone: they were the headings of
pages that no longer exist, on a tab that already says which app this is;
- the settings tab gains "Cim es kulcsok" with one address row, and the disabled subdomain input
that said the same thing is skipped for an installed app (the install wizard still asks);
- the "this app is already installed, the settings are read-only" BANNER becomes the hint under
the fields it is about;
- the install wizard gets the same back link, hero and column, so pressing Telepites does not
land the household on a page that looks like a different product.
THE AUTO-UPDATE SUCCESS BANNER IS GONE. It was a full-width blue box above the tabs, level with
"the update failed" and "your files are missing". A success that asked nothing of the household is
a date, and it is now meta text in the hero. Every banner that DOES need a decision — a failed
update, a hold, lost files, a missing drive, an interrupted install — is untouched, above the tabs,
exactly where R-571 puts it.
Tests: TestAppPage_OneColumnLayout (one column, one aria-current, no card-shaped section, no
success banner), TestAppPage_AutoUpdateSuccessIsMetaNotBanner, TestAppPage_CalloutsOnlyForAskAndWarn
(one blue callout on a waiting app and it comes first; one amber on a same-disk backup; none on the
good case). Four red-proofs recorded in REPORT.
app_row_dedup_gate convicted `.app-row`, correctly: that class already means "a row in the app
list". The app page's label/value row is `.app-field-row`. Restoring the list's own rule after an
over-broad rename was part of the fix.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0117kRTSgs3rJWUFGxyoWN6Y
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
{{define "app_data"}}
|
||||
{{template "layout_start" .}}
|
||||
<div class="app-page">
|
||||
{{template "app_shell_start" .}}
|
||||
|
||||
{{/* „ADATOK ES MENTES" — everything about this app's FILES, in the order the household asks it:
|
||||
@@ -17,7 +18,7 @@
|
||||
{{if .FlashError}}<div class="monitoring-banner monitoring-banner-red">{{.FlashError}}</div>{{end}}
|
||||
|
||||
{{if .DataPathCards}}
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<h3>{{T "app_info.hova_tegyem_a_fajlokat"}}</h3>
|
||||
<p class="form-hint">{{T "app_info.ezeket_a_mappakat_a_fajlkezeloben"}}</p>
|
||||
<div class="datapath-list">
|
||||
@@ -35,7 +36,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if and .Stack.Deployed .MigrateTargets}}
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
<h3>{{T "app_info.athelyezes_masik_tarhelyre"}}</h3>
|
||||
<p class="form-hint">{{T "app_info.ennek_az_alkalmazasnak_az_adatait"}}</p>
|
||||
<div style="display:flex;gap:.5rem;align-items:center;flex-wrap:wrap;margin-top:.5rem">
|
||||
@@ -97,8 +98,10 @@ function appMigrate(btn,app,label){
|
||||
</div>
|
||||
|
||||
{{if .Tier2}}
|
||||
<h3 style="margin-top:1.5rem">{{T "tier2_config.2_mentes_beallitasa"}}</h3>
|
||||
<div class="monitor-card">
|
||||
{{/* v0.316.0: a SECTION, not a page title over a card. „2. mentés beállítása — <app>" was the
|
||||
heading of a page that no longer exists; the tab already says which app this is. */}}
|
||||
<div class="app-section">
|
||||
<h3>{{T "tier2_config.2_mentes_beallitasa"}}</h3>
|
||||
{{with .Tier2}}
|
||||
<p style="color:var(--text-3);font-size:.9rem;margin-top:0">
|
||||
{{T "tier2_config.a_2_mentes_egy_masik"}}
|
||||
@@ -106,19 +109,19 @@ function appMigrate(btn,app,label){
|
||||
|
||||
{{if not .IsHDDApp}}
|
||||
{{if eq $.SystemBackup "protected"}}
|
||||
<div class="monitoring-banner monitoring-banner-yellow" style="margin-top:1rem" data-system-backup="protected">
|
||||
<div class="app-callout" style="margin-top:1rem" data-system-backup="protected">
|
||||
{{T "tier2_config.rendszerlemez.vedett"}}
|
||||
</div>
|
||||
{{else if eq $.SystemBackup "same_disk"}}
|
||||
<div class="monitoring-banner monitoring-banner-yellow" style="margin-top:1rem" data-system-backup="same_disk">
|
||||
<div class="app-callout app-callout-warn" style="margin-top:1rem" data-system-backup="same_disk">
|
||||
{{T "tier2_config.rendszerlemez.ugyanaz"}}
|
||||
</div>
|
||||
{{else if eq $.SystemBackup "absent"}}
|
||||
<div class="monitoring-banner monitoring-banner-red" style="margin-top:1rem" data-system-backup="absent">
|
||||
<div class="app-callout app-callout-warn" style="margin-top:1rem" data-system-backup="absent">
|
||||
{{T "tier2_config.rendszerlemez.hianyzik"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="monitoring-banner monitoring-banner-yellow" style="margin-top:1rem" data-system-backup="unknown">
|
||||
<div class="app-callout app-callout-warn" style="margin-top:1rem" data-system-backup="unknown">
|
||||
{{T "tier2_config.rendszerlemez.ismeretlen"}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -151,7 +154,7 @@ function appMigrate(btn,app,label){
|
||||
</div>
|
||||
|
||||
{{if .EffectiveIsSSD}}
|
||||
<div class="monitoring-banner monitoring-banner-yellow" style="margin-top:1rem">
|
||||
<div class="app-callout app-callout-warn" style="margin-top:1rem">
|
||||
{{T "tier2_config.jelenleg_csak_a_belso_ssd"}}
|
||||
</div>
|
||||
{{end}}
|
||||
@@ -186,9 +189,9 @@ function appMigrate(btn,app,label){
|
||||
{{end}}
|
||||
|
||||
{{if .ExportOffered}}
|
||||
<h3 style="margin-top:2rem">{{T "app_export.exportalas"}}</h3>
|
||||
<div class="card" style="max-width:700px">
|
||||
<h3>{{T "app_export.mentes_helye"}}</h3>
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_export.exportalas"}}</h3>
|
||||
<h4 style="font-size:14px;font-weight:600;margin:12px 0 6px">{{T "app_export.mentes_helye"}}</h4>
|
||||
<select id="destDrive" onchange="loadEstimate()" style="width:100%;padding:.5rem;margin-bottom:1rem">
|
||||
<option value="">{{T "app_export.valassz_tarolot"}}</option>
|
||||
{{range .Drives}}
|
||||
@@ -495,5 +498,6 @@ function showDone(data) {
|
||||
</div>
|
||||
|
||||
{{template "app_press_script" .}}
|
||||
</div>{{/* .app-page */}}
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
|
||||
@@ -60,6 +60,10 @@
|
||||
<div class="form-section">
|
||||
<h4>{{T "deploy.beallitasok"}}</h4>
|
||||
{{range .UserFields}}
|
||||
{{/* v0.316.0: an INSTALLED app shows its address once, as the „Cim" row above these
|
||||
fields, so the disabled subdomain input saying the same thing is skipped. The
|
||||
install wizard still asks for it — this is the read-only view's duplicate only. */}}
|
||||
{{if and $.AlreadyDeployed (eq .Type "subdomain")}}{{continue}}{{end}}
|
||||
<div class="form-group">
|
||||
<label for="field-{{.EnvVar}}">
|
||||
{{.Label}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{define "app_guide"}}
|
||||
{{template "layout_start" .}}
|
||||
<div class="app-page">
|
||||
{{template "app_shell_start" .}}
|
||||
|
||||
{{/* „UTMUTATO" — the app's own copy, and the WHOLE page for an app that is not installed yet.
|
||||
@@ -27,7 +28,7 @@
|
||||
{{/* The install button lives on this tab, because this tab IS the page for a not-installed app.
|
||||
The server refuses a non-installable template regardless; hiding the button keeps the page
|
||||
from offering an action that would be rejected. */}}
|
||||
<div class="app-info-card" style="margin-top:1rem">
|
||||
<div class="app-section" style="margin-top:1rem">
|
||||
{{if canInstall .Meta}}
|
||||
<a href="/apps/{{.Meta.Slug}}/install" class="btn btn-primary" data-install-cta="1"
|
||||
onclick="return checkBeforeDeploy(event, '{{.Stack.Name}}')">{{T "app_info.telepites"}}</a>
|
||||
@@ -37,9 +38,9 @@
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="app-info-grid">
|
||||
<div class="app-sections">
|
||||
{{if .AppInfo.UseCases}}
|
||||
<div class="app-info-card">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.mire_hasznalhato"}}</h3>
|
||||
<ul class="app-info-list">
|
||||
{{range .AppInfo.UseCases}}<li>{{.}}</li>{{end}}
|
||||
@@ -48,7 +49,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if .AppInfo.FirstSteps}}
|
||||
<div class="app-info-card">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.elso_lepesek"}}</h3>
|
||||
<ol class="app-info-list">
|
||||
{{range .AppInfo.FirstSteps}}<li>{{.}}</li>{{end}}
|
||||
@@ -57,7 +58,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if .AppInfo.Prerequisites}}
|
||||
<div class="app-info-card">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.elofeltetelek"}}</h3>
|
||||
<ul class="app-info-list">
|
||||
{{range .AppInfo.Prerequisites}}<li>{{.}}</li>{{end}}
|
||||
@@ -70,7 +71,7 @@
|
||||
{{if .AppInfo.Guide.Rendered}}
|
||||
<div class="app-guide" data-guide="1" data-guide-checked="{{.AppInfo.Guide.CheckedVersion}}">
|
||||
{{- range .AppInfo.Guide.Sections}}
|
||||
<div class="app-info-card" style="margin-top:1rem" data-guide-section="1">
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-section="1">
|
||||
<h3>{{.Title}}</h3>
|
||||
{{if .Intro}}<p>{{.Intro}}</p>{{end}}
|
||||
{{if .Steps}}
|
||||
@@ -81,7 +82,7 @@
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .AppInfo.Guide.FAQ}}
|
||||
<div class="app-info-card" style="margin-top:1rem" data-guide-faq="1">
|
||||
<div class="app-section" style="margin-top:1rem" data-guide-faq="1">
|
||||
<h3>{{T "app_page.faq"}}</h3>
|
||||
<dl class="app-faq">
|
||||
{{- range .AppInfo.Guide.FAQ}}
|
||||
@@ -96,14 +97,15 @@
|
||||
<p class="form-hint" style="margin-top:1rem" data-guide-missing="1">{{T "app_page.guide_missing"}}</p>
|
||||
{{end}}
|
||||
|
||||
<div class="app-info-grid">
|
||||
<div class="app-sections">
|
||||
{{if .AppInfo.DocsURL}}
|
||||
<div class="app-info-card">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.dokumentacio"}}</h3>
|
||||
<p><a href="{{.AppInfo.DocsURL}}" target="_blank" class="app-info-link">{{T "app_info.hivatalos_dokumentacio"}}</a></p>
|
||||
</div>
|
||||
{{end}}</div>
|
||||
|
||||
{{template "app_press_script" .}}
|
||||
</div>{{/* .app-page */}}
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{define "app_logs"}}
|
||||
{{template "layout_start" .}}
|
||||
<div class="app-page app-page-wide">
|
||||
{{template "app_shell_start" .}}
|
||||
|
||||
{{/* „NAPLO" — today's logs page inside the shell. Its live poller runs ONLY on this tab, which is
|
||||
@@ -73,5 +74,6 @@
|
||||
</script>
|
||||
</div>
|
||||
|
||||
</div>{{/* .app-page */}}
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{{define "app_settings"}}
|
||||
{{template "layout_start" .}}
|
||||
<div class="app-page">
|
||||
{{template "app_shell_start" .}}
|
||||
|
||||
{{/* „BEALLITASOK" — the tab an installed app opens on.
|
||||
@@ -20,10 +21,33 @@
|
||||
{{if .FlashSuccess}}<div class="flash flash-success">{{.FlashSuccess}}</div>{{end}}
|
||||
{{if .FlashError}}<div class="flash flash-error">{{.FlashError}}</div>{{end}}
|
||||
|
||||
{{/* 1. MUKODES. Stop asks first — it takes the app away from everyone in the house, and the
|
||||
{{/* 1. WHAT THE APP IS WAITING FOR — the generic after-start card (Part 4, D2 = A).
|
||||
FIRST on the tab (v0.316.0): a household opening a Plex that wants its claim code should
|
||||
meet the code box, not a Restart button. It is the page's one BLUE callout — a request,
|
||||
never a fault, so never amber.
|
||||
The catalog says a value is asked after the The catalog says a value is asked after the
|
||||
app starts and supplies its label; the ACTION is Go code in after_start.go, keyed by slug
|
||||
and env var. A field with no registered action renders nothing and POSTs 404 — the catalog
|
||||
cannot make the box run anything. */}}
|
||||
{{- range .AfterStartFields}}
|
||||
<div class="app-section app-callout app-callout-ask" data-afterstart="{{.EnvVar}}">
|
||||
<h3>{{T "app_page.after_start_title"}}</h3>
|
||||
{{if .Description}}<p>{{.Description}}</p>{{end}}
|
||||
<div class="input-with-button">
|
||||
<input type="text" id="after-start-{{.EnvVar}}" class="form-control"
|
||||
placeholder="{{.Placeholder}}" autocomplete="off" spellcheck="false"
|
||||
aria-label="{{.Label}}">
|
||||
<button type="button" class="btn btn-sm btn-primary"
|
||||
onclick="appAfterStart(this, '{{$.Meta.Slug}}', '{{.EnvVar}}')">{{T "app_page.after_start_btn"}}</button>
|
||||
</div>
|
||||
<span class="form-hint app-press-err" style="display:none;color:var(--red)"></span>
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
{{/* 2. MUKODES. Stop asks first — it takes the app away from everyone in the house, and the
|
||||
protected stacks refuse it server-side regardless (traefik, cloudflared, the controller
|
||||
itself and always samba). */}}
|
||||
<div class="app-info-card" id="app-run-card" data-run-card="1">
|
||||
<div class="app-section" id="app-run-card" data-run-card="1">
|
||||
<h3>{{T "app_page.mukodes"}}</h3>
|
||||
<p class="form-hint" data-run-state="{{.Stack.State}}">{{stateLabel .Stack.State}}</p>
|
||||
<div style="display:flex;gap:.5rem;flex-wrap:wrap;margin-top:.5rem">
|
||||
@@ -36,15 +60,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{/* 2. WHAT THE APP STILL WANTS FROM THE HOUSEHOLD. */}}
|
||||
{{/* 3. THE REST OF WHAT THE APP WANTS FROM THE HOUSEHOLD. */}}
|
||||
{{- if .InstallHoldClosed}}
|
||||
<div class="app-info-card" style="margin-top:1rem" id="install-hold-card">
|
||||
<div class="app-section" style="margin-top:1rem" id="install-hold-card">
|
||||
<h3>{{T "app_info.install_hold_title"}}</h3>
|
||||
<p>{{T "app_info.install_hold_closed"}}</p>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .SetupGateClosed}}
|
||||
<div class="app-info-card" style="margin-top:1rem" id="setup-gate-card">
|
||||
<div class="app-section" style="margin-top:1rem" id="setup-gate-card">
|
||||
<h3>{{T "app_info.setup_gate_title"}}</h3>
|
||||
<p>{{T "app_info.setup_gate_closed"}}</p>{{- if .SignupNative}}
|
||||
<p class="form-hint" data-restarts-once="true">{{T "app_info.restarts_once"}}</p>{{- end}}
|
||||
@@ -59,26 +83,8 @@
|
||||
</div>
|
||||
{{- end}}
|
||||
|
||||
{{/* The generic after-start card (Part 4, D2 = A). The catalog says a value is asked after the
|
||||
app starts and supplies its label; the ACTION is Go code in after_start.go, keyed by slug and
|
||||
env var. A field with no registered action renders nothing and POSTs 404 — the catalog
|
||||
cannot make the box run anything. */}}
|
||||
{{- range .AfterStartFields}}
|
||||
<div class="app-info-card after-start-card" data-afterstart="{{.EnvVar}}">
|
||||
<h3>{{T "app_page.after_start_title"}}</h3>
|
||||
{{if .Description}}<p>{{.Description}}</p>{{end}}
|
||||
<div class="input-with-button">
|
||||
<input type="text" id="after-start-{{.EnvVar}}" class="form-control"
|
||||
placeholder="{{.Placeholder}}" autocomplete="off" spellcheck="false"
|
||||
aria-label="{{.Label}}">
|
||||
<button type="button" class="btn btn-sm btn-primary"
|
||||
onclick="appAfterStart(this, '{{$.Meta.Slug}}', '{{.EnvVar}}')">{{T "app_page.after_start_btn"}}</button>
|
||||
</div>
|
||||
<span class="form-hint app-press-err" style="display:none;color:var(--red)"></span>
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if .CloseSignupOffered}}
|
||||
<div class="app-info-card" style="margin-top:1rem" id="close-signup-card">
|
||||
<div class="app-section" style="margin-top:1rem" id="close-signup-card">
|
||||
<h3>{{T "app_info.signup_title"}}</h3>
|
||||
<p>{{T "app_info.close_signup_text"}}</p>{{- if .SignupNative}}
|
||||
<p class="form-hint" data-restarts-once="true">{{T "app_info.restarts_once"}}</p>{{- end}}
|
||||
@@ -87,7 +93,7 @@
|
||||
</div>
|
||||
{{- end}}
|
||||
{{- if or .SignupClosed .SignupOpenUntil}}
|
||||
<div class="app-info-card" style="margin-top:1rem" id="signup-card">
|
||||
<div class="app-section" style="margin-top:1rem" id="signup-card">
|
||||
<h3>{{T "app_info.signup_title"}}</h3>
|
||||
{{- if .SignupOpenUntil}}
|
||||
<p>{{T "app_info.signup_open_until"}} {{.SignupOpenUntil}}</p>
|
||||
@@ -114,9 +120,9 @@
|
||||
{{- end}}
|
||||
|
||||
{{/* The logins, each already its own guarded card on the details page. */}}
|
||||
<div class="app-info-grid">
|
||||
<div class="app-sections">
|
||||
{{if .InitialCreds}}
|
||||
<div class="app-info-card">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.kezdeti_belepesi_adatok"}}</h3>
|
||||
<table class="initcred-table">
|
||||
{{if .InitialCreds.Username}}
|
||||
@@ -151,7 +157,7 @@
|
||||
{{end}}
|
||||
|
||||
{{if and .AppInfo.DefaultCreds (not .DefaultLoginReplaced)}}
|
||||
<div class="app-info-card">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_info.alapertelmezett_belepes"}}</h3>
|
||||
<p class="app-info-creds">{{.AppInfo.DefaultCreds}}</p>
|
||||
{{if .KnownLoginLine}}<p class="app-info-creds-warn" id="known-login-line">{{.KnownLoginLine}}</p>{{else}}<p class="app-info-creds-warn">{{T "app_info.az_elso_bejelentkezes_utan_azonnal_2"}}</p>{{end}}
|
||||
@@ -162,10 +168,10 @@
|
||||
</div>
|
||||
{{end}}</div>
|
||||
|
||||
{{/* 3. THE SETTINGS. Everything below came from the install page's deployed mode, unchanged. */}}
|
||||
<div class="alert alert-info">
|
||||
{{T "deploy.ez_az_alkalmazas_mar_telepitve"}}
|
||||
</div>
|
||||
{{/* 3. THE SETTINGS. v0.316.0: the „this app is already installed, the settings below are
|
||||
read-only" BANNER is gone — it was a box saying what a one-line hint says, on a tab the
|
||||
household reached by pressing „Beallitasok" on an installed app. The hint lives with the
|
||||
fields it is about, at the bottom of this tab. */}}
|
||||
{{if .StorageInfo}}
|
||||
<div class="deploy-storage-info">
|
||||
<h4>{{T "deploy.adattarolas"}}</h4>
|
||||
@@ -536,9 +542,19 @@
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
<div class="app-section">
|
||||
<h3>{{T "app_page.address_and_keys"}}</h3>
|
||||
{{if .EffectiveSubdomain}}
|
||||
<div class="app-field-row">
|
||||
<div class="app-field-label">{{T "app_page.address"}}</div>
|
||||
<div class="app-field-value" data-app-address-row="1">{{.EffectiveSubdomain}}.{{.Domain}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="deploy-form" class="deploy-form">
|
||||
{{template "app_deploy_fields" .}}
|
||||
</form>
|
||||
</form>
|
||||
<p class="app-field-hint" data-readonly-hint="1">{{T "app_page.installed_readonly"}}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{if .InitialCreds}}
|
||||
@@ -711,5 +727,6 @@ function deleteStaleDataRun(stackName, stalePath, btn) {
|
||||
|
||||
</script>
|
||||
|
||||
</div>{{/* .app-page */}}
|
||||
{{template "layout_end" .}}
|
||||
{{end}}
|
||||
|
||||
@@ -11,36 +11,45 @@
|
||||
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>
|
||||
<a href="/stacks" class="app-back">{{T "app_info.alkalmazasok"}}</a>
|
||||
|
||||
<div class="app-info-hero">
|
||||
<img class="app-info-logo" src="{{logoURL .Meta.Slug}}"
|
||||
<div class="app-hero">
|
||||
<img class="app-hero-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 class="app-hero-text">
|
||||
<h2 class="app-hero-name">{{.Meta.DisplayName}}</h2>
|
||||
{{if .AppInfo.Tagline}}<p class="app-hero-tagline">{{.AppInfo.Tagline}}</p>{{else if .Meta.Description}}<p class="app-hero-tagline">{{.Meta.Description}}</p>{{end}}
|
||||
<div class="app-chips">
|
||||
{{if .Deployed}}
|
||||
<span class="app-chip" data-state-chip="{{.Stack.State}}">● {{stateLabel .Stack.State}}</span>
|
||||
{{if .Stack.Orphaned}}<span class="app-chip">{{T "common.elavult"}}</span>{{end}}
|
||||
{{template "meta_badge" (lifecycleBadge .Meta)}}
|
||||
{{template "meta_badge" (updateBadge .Stack)}}{{if and (gt .Stack.LadderStepsLeft 0) (not .Stack.HoldReason)}}<span class="app-meta" data-ladder-steps="{{.Stack.LadderStepsLeft}}">{{T "app_info.ladder_steps_left"}} {{.Stack.LadderStepsLeft}}</span>{{end}}
|
||||
{{/* v0.316.0: the auto-update SUCCESS line is meta text here, not a banner below.
|
||||
A success that asked nothing of the household is a date, not news; the banners
|
||||
under the hero stay for the things that DO need them (a failed update, a hold,
|
||||
lost files, a missing drive) — R-571's placement rule is untouched. */}}
|
||||
{{if .EffectiveSubdomain}}<span class="app-meta" data-app-address="1">{{.EffectiveSubdomain}}.{{.Domain}}</span>{{end}}
|
||||
{{if .AutoUpdateLine}}<span class="app-meta" data-auto-update="done">{{.AutoUpdateLine}}</span>{{end}}
|
||||
{{else}}
|
||||
<span class="app-chip">~{{.Meta.Resources.MemRequest}} RAM</span>
|
||||
{{if .Meta.Category}}<span class="app-chip">{{.Meta.Category}}</span>{{end}}
|
||||
{{if .Meta.Resources.NeedsHDD}}<span class="app-chip">{{T "common.hdd_szukseges"}}</span>{{end}}
|
||||
{{if .Meta.Resources.HungarianUI}}<span class="app-chip">{{T "common.magyar_felulet"}}</span>{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{if and .Deployed .AppOperational .EffectiveSubdomain}}
|
||||
<div class="app-hero-actions">
|
||||
<a href="https://{{.EffectiveSubdomain}}.{{.Domain}}{{.Meta.OpenPath}}" target="_blank" class="btn btn-primary" data-app-open="1">{{T "app_info.megnyitas"}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Deployed .Stack.Orphaned}}
|
||||
<div class="app-hero-actions">
|
||||
<button class="btn btn-sm btn-danger" onclick="deleteOrphanStack('{{.Stack.Name}}')">{{T "common.torles"}}</button>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{template "app_banners" .}}
|
||||
@@ -77,8 +86,6 @@
|
||||
<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}}
|
||||
|
||||
@@ -9,35 +9,26 @@
|
||||
Vissza and Megsem go to the APP, not to the list: the household got here from the app's page
|
||||
and that is where cancelling belongs. */}}
|
||||
|
||||
<div class="page-header">
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
<a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline" data-install-back="1">{{T "deploy.vissza"}}</a>
|
||||
<h2>{{.Meta.DisplayName}} — {{T "deploy.telepites"}}</h2>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:.5rem">
|
||||
<a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline"><svg class="ico ico-sm"><use href="#i-info"/></svg> {{T "deploy.reszletek"}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<a href="/apps/{{.Meta.Slug}}" class="app-back" data-install-back="1">{{T "deploy.vissza"}}</a>
|
||||
|
||||
|
||||
<div class="deploy-container">
|
||||
<div class="app-page deploy-container">
|
||||
{{if .FlashSuccess}}<div class="flash flash-success">{{.FlashSuccess}}</div>{{end}}
|
||||
{{if .FlashError}}<div class="flash flash-error">{{.FlashError}}</div>{{end}}
|
||||
{{- if .KnownLoginLine}}<div class="flash flash-error" id="known-login-line">{{.KnownLoginLine}}</div>{{end}}
|
||||
<div class="deploy-info">
|
||||
<img class="deploy-logo" src="{{.LogoURL}}" alt="" data-fallback="/static/app-placeholder.svg"
|
||||
{{/* v0.316.0: the same hero as the app page, so pressing „Telepites" does not land the
|
||||
household on a page that looks like a different product. */}}
|
||||
<div class="app-hero">
|
||||
<img class="app-hero-logo" src="{{.LogoURL}}" alt="" data-fallback="/static/app-placeholder.svg"
|
||||
onerror="if(!this.dataset.step){this.dataset.step='1';this.src='{{.LogoPNGURL}}';}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>
|
||||
<h3>{{.Meta.DisplayName}}</h3>
|
||||
{{if .Meta.Description}}<p>{{.Meta.Description}}</p>{{end}}
|
||||
<div class="metarows">
|
||||
{{if .Meta.Resources.MemRequest}}<span class="metarow"><svg class="ico"><use href="#i-memory-stick"/></svg><span class="mono">~{{.Meta.Resources.MemRequest}}</span></span>{{end}}
|
||||
{{if .Meta.Resources.NeedsHDD}}<span class="metarow"><svg class="ico"><use href="#i-hard-drive"/></svg>{{T "common.hdd_szukseges"}}</span>{{end}}
|
||||
{{if .Meta.Resources.HungarianUI}}<span class="metarow"><svg class="ico"><use href="#i-check"/></svg>{{T "common.magyar_felulet"}}</span>{{end}}
|
||||
<div class="app-hero-text">
|
||||
<h2 class="app-hero-name">{{.Meta.DisplayName}} — {{T "deploy.telepites"}}</h2>
|
||||
{{if .Meta.Description}}<p class="app-hero-tagline">{{.Meta.Description}}</p>{{end}}
|
||||
<div class="app-chips">
|
||||
{{if .Meta.Resources.MemRequest}}<span class="app-chip">~{{.Meta.Resources.MemRequest}} RAM</span>{{end}}
|
||||
{{if .Meta.Resources.NeedsHDD}}<span class="app-chip">{{T "common.hdd_szukseges"}}</span>{{end}}
|
||||
{{if .Meta.Resources.HungarianUI}}<span class="app-chip">{{T "common.magyar_felulet"}}</span>{{end}}
|
||||
<a href="/apps/{{.Meta.Slug}}" class="app-meta" style="text-decoration:none">{{T "deploy.reszletes_leiras_kepernyokepek"}}</a>
|
||||
</div>
|
||||
<a href="/apps/{{.Meta.Slug}}" class="btn btn-sm btn-outline" style="margin-top:0.5rem">
|
||||
{{T "deploy.reszletes_leiras_kepernyokepek"}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -389,7 +389,7 @@ h3 {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.stack-list .app-row {
|
||||
.stack-list .app-field-row {
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-top: 1px solid var(--line-soft);
|
||||
@@ -1300,50 +1300,138 @@ a.stat-card:hover {
|
||||
.login-footer a:hover { color: var(--blue); }
|
||||
|
||||
/* --- App Info Page --- */
|
||||
/* THE APP PAGE'S TAB BAR (v0.315.0) — links, not panels.
|
||||
Design system v2: 2px radius, hairline borders, no shadow, and blue reserved for the tab the
|
||||
household is ON. An inactive tab is --text-2: a tab bar is navigation, not a deviation. */
|
||||
/* ── THE APP PAGE (v0.316.0) — one column, hairlines, boxes only where they mean something ───────
|
||||
Operator-approved mockup, 2026-10-15. The rules, and why each one:
|
||||
ONE COLUMN of ONE WIDTH. Boxes of different widths read as a page assembled by accident.
|
||||
SECTIONS ARE SEPARATED BY HAIRLINES, not drawn as cards. A card says "this is a thing you act
|
||||
on"; most of an app page is a thing you READ, so a card there is a promise it cannot keep.
|
||||
A BOX MEANS SOMETHING. Exactly two exist: blue when the app is waiting for the household, amber
|
||||
when something is risky. The exception-colour principle, applied to shape as well as colour.
|
||||
Tokens only — no raw hexes, 2px radius, no shadows. */
|
||||
|
||||
.app-page {
|
||||
max-width: 820px;
|
||||
padding: 32px 48px 64px;
|
||||
}
|
||||
/* The log is the one tab whose content is genuinely wide: log lines are long, and wrapping them at
|
||||
820px makes them unreadable. */
|
||||
.app-page-wide { max-width: none; }
|
||||
|
||||
.app-back {
|
||||
display: inline-block;
|
||||
font-size: 13px;
|
||||
color: var(--text-2);
|
||||
text-decoration: none;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.app-back:hover { color: var(--text-1); }
|
||||
|
||||
/* HERO — no box, no border. The app's identity is a heading, not a card. */
|
||||
.app-hero {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.app-hero-logo {
|
||||
width: 64px; height: 64px;
|
||||
min-width: 64px;
|
||||
border: 1px solid var(--line);
|
||||
background: var(--bg-1);
|
||||
border-radius: var(--radius);
|
||||
object-fit: contain;
|
||||
padding: 8px;
|
||||
}
|
||||
.app-hero-text { flex: 1 1 320px; min-width: 0; }
|
||||
.app-hero-name { font-size: 26px; font-weight: 700; color: var(--text-1); margin: 0; line-height: 1.2; }
|
||||
.app-hero-tagline { font-size: 15px; color: var(--text-2); margin: 4px 0 10px; }
|
||||
.app-hero-actions { margin-left: auto; }
|
||||
|
||||
.app-chips { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
|
||||
.app-chip {
|
||||
font-size: 12px; font-weight: 600;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius);
|
||||
padding: 3px 8px;
|
||||
color: var(--text-1);
|
||||
white-space: nowrap;
|
||||
}
|
||||
/* The meta line carries what used to be a full-width green banner saying the automatic update
|
||||
worked. A success that needed no decision is not news; it is a date. */
|
||||
.app-meta { font-size: 12px; color: var(--text-3); }
|
||||
|
||||
/* TABS — links with a line under the open one. No pills, no fills. */
|
||||
.app-tabs {
|
||||
display: flex;
|
||||
gap: .25rem;
|
||||
gap: 28px;
|
||||
flex-wrap: wrap;
|
||||
margin: 1.25rem 0 1.5rem;
|
||||
margin: 28px 0 8px;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
.app-tab {
|
||||
padding: .55rem 1rem;
|
||||
font-size: 15px; font-weight: 600;
|
||||
padding: 12px 0;
|
||||
color: var(--text-2);
|
||||
text-decoration: none;
|
||||
font-size: .9rem;
|
||||
border: 1px solid transparent;
|
||||
border-bottom: none;
|
||||
border-radius: var(--radius) var(--radius) 0 0;
|
||||
/* The bar's own 1px line is what the active tab sits on, so each tab reaches it. */
|
||||
border-bottom: 2px solid transparent;
|
||||
margin-bottom: -1px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.app-tab:hover { color: var(--text-1); background: var(--bg-1); }
|
||||
.app-tab-active {
|
||||
color: var(--blue-bright);
|
||||
background: var(--bg-1);
|
||||
border-color: var(--line);
|
||||
border-bottom: 1px solid var(--bg-1);
|
||||
}
|
||||
.app-tab-active:hover { color: var(--blue-bright); }
|
||||
@media (max-width: 640px) {
|
||||
.app-tabs { gap: 0; }
|
||||
.app-tab { padding: .55rem .6rem; font-size: .85rem; }
|
||||
}
|
||||
.app-tab:hover { color: var(--text-1); }
|
||||
.app-tab-active { color: var(--text-1); border-bottom-color: var(--blue-bright); }
|
||||
|
||||
/* „Még egy lépés" — a value the app wants once it is running (Part 4). It is a request, not a
|
||||
fault, so it stays neutral: the blue hairline marks it as the thing to do next. */
|
||||
.after-start-card { border-left: 2px solid var(--blue); }
|
||||
/* SECTIONS — a hairline between, nothing around. */
|
||||
.app-section {
|
||||
padding: 28px 0;
|
||||
border-bottom: 1px solid var(--line-soft);
|
||||
background: none;
|
||||
border-left: 0; border-right: 0; border-top: 0;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.app-section:last-child { border-bottom: 0; }
|
||||
.app-section > h3, .app-section > h4 {
|
||||
font-size: 17px; font-weight: 700; color: var(--text-1); margin: 0 0 6px;
|
||||
}
|
||||
.app-section > p { font-size: 14px; color: var(--text-2); line-height: 1.55; margin: 0 0 16px; }
|
||||
|
||||
/* ROWS — label and value, one grid so every value on the page starts at the same x. */
|
||||
.app-field-row {
|
||||
display: grid;
|
||||
grid-template-columns: 200px minmax(0, 1fr);
|
||||
gap: 16px;
|
||||
padding: 10px 0;
|
||||
align-items: baseline;
|
||||
}
|
||||
.app-field-label { font-size: 14px; color: var(--text-2); }
|
||||
.app-field-value { font-size: 14px; color: var(--text-1); min-width: 0; }
|
||||
.app-field-hint { font-size: 12px; color: var(--text-3); margin: 6px 0 0; }
|
||||
|
||||
/* CALLOUTS — the only boxes on the page. */
|
||||
.app-callout {
|
||||
padding: 14px 16px;
|
||||
border-radius: var(--radius);
|
||||
font-size: 14px;
|
||||
border: 1px solid var(--line);
|
||||
}
|
||||
.app-callout > h3, .app-callout > h4 { font-size: 15px; font-weight: 700; margin: 0 0 6px; color: var(--text-1); }
|
||||
/* „The app is waiting for you" — a request, never a fault, so blue and never amber. */
|
||||
.app-callout-ask { border-color: var(--blue); background: var(--blue-dim); }
|
||||
/* „Something here is risky" — the exception colour, and only on a deviation. */
|
||||
.app-callout-warn { border-color: var(--warn); background: var(--warn-dim); }
|
||||
|
||||
/* The guide's FAQ. */
|
||||
.app-faq { margin: .5rem 0 0; }
|
||||
.app-faq dt { color: var(--text-1); font-weight: 600; margin-top: .85rem; }
|
||||
.app-faq dd { color: var(--text-2); margin: .25rem 0 0; padding: 0; }
|
||||
|
||||
@media (max-width: 720px) {
|
||||
.app-page { padding: 20px 16px 48px; }
|
||||
.app-field-row { grid-template-columns: 1fr; gap: 4px; }
|
||||
.app-hero-actions { margin-left: 0; flex-basis: 100%; }
|
||||
.app-tabs { gap: 18px; }
|
||||
.app-tab { font-size: 14px; }
|
||||
}
|
||||
|
||||
.app-info-hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -2759,7 +2847,7 @@ a.stat-card:hover {
|
||||
.no-js .nav-burger { display: none; }
|
||||
.content { margin-left: 0; padding: 1rem; }
|
||||
body { flex-direction: column; }
|
||||
.app-row { flex-wrap: wrap; }
|
||||
.app-field-row { flex-wrap: wrap; }
|
||||
.app-row-actions { width: 100%; justify-content: flex-end; }
|
||||
.stack-grid { grid-template-columns: 1fr; }
|
||||
/* R-907: a page header (title, domain chip, buttons) wraps instead of running off the screen; on the
|
||||
|
||||
Reference in New Issue
Block a user