148 lines
9.4 KiB
PHP
148 lines
9.4 KiB
PHP
{{--
|
|
"We are updating" — one panel, used in both places it can appear.
|
|
|
|
It showed up twice with two different faces: the console's own overlay in the
|
|
app's light tokens while the application was still up, and then this, the
|
|
503 page, once the containers went down. Two designs for one event, one
|
|
replacing the other mid-update.
|
|
|
|
The styles are inline and self-contained rather than Tailwind classes,
|
|
because the other place this renders is the 503 page — shown while the
|
|
application is mid-deploy or freshly installed, exactly when the Vite
|
|
manifest may not exist. A stylesheet reference there would throw instead of
|
|
rendering the reassurance the visitor came for.
|
|
|
|
Prefixed class names: this is injected into the console's own document,
|
|
which has a stylesheet of its own, and `.badge` is not a name to claim
|
|
globally.
|
|
|
|
── On the colours, because they were wrong twice ────────────────────────
|
|
Every value below is a copy of a token from resources/css/portal-tokens.css.
|
|
They were not: the first version used Tailwind's slate ramp (#0f172a,
|
|
#64748b, #e2e8f0), which is blue-tinted, against a product whose neutrals
|
|
are warm. Even in daylight the panel read cooler than everything around it.
|
|
|
|
And it carried a `prefers-color-scheme: dark` block, which nothing else in
|
|
this product has. On a phone set to dark mode the update screen — and only
|
|
the update screen — turned dark blue. Reported exactly that way. A dark mode
|
|
for one page is not a dark mode; it is one page that does not match.
|
|
--}}
|
|
<style>
|
|
/* The fonts, declared here too. The console loads them through Vite, but
|
|
the 503 page has no stylesheet at all — so the maintenance screen was the
|
|
one surface rendering in the system sans while every other page was IBM
|
|
Plex. Same files the placeholder uses; if they are missing the page
|
|
degrades to a system sans and still reads. */
|
|
@font-face{font-family:"Plex Sans";src:url("/fonts/ibm-plex-sans-latin-400-normal.woff2") format("woff2");font-weight:400;font-display:swap}
|
|
@font-face{font-family:"Plex Sans";src:url("/fonts/ibm-plex-sans-latin-500-normal.woff2") format("woff2");font-weight:500;font-display:swap}
|
|
@font-face{font-family:"Plex Sans";src:url("/fonts/ibm-plex-sans-latin-600-normal.woff2") format("woff2");font-weight:600;font-display:swap}
|
|
@font-face{font-family:"Plex Sans";src:url("/fonts/ibm-plex-sans-latin-700-normal.woff2") format("woff2");font-weight:700;font-display:swap}
|
|
@font-face{font-family:"Plex Mono";src:url("/fonts/ibm-plex-mono-latin-400-normal.woff2") format("woff2");font-weight:400;font-display:swap}
|
|
|
|
.cpu-wrap {
|
|
/* portal-tokens.css: --bg, --text-strong, --text-muted, --accent,
|
|
--accent-text, --surface, --border, --surface-2 */
|
|
--cpu-bg: #f6f6f8; --cpu-fg: #17171c; --cpu-muted: #6e6e7a;
|
|
--cpu-accent: #f97316; --cpu-accent-ink: #b8500a;
|
|
--cpu-card: #ffffff; --cpu-card-2: #fafafb; --cpu-line: #e9e9ee;
|
|
display: grid; place-items: center; text-align: center;
|
|
padding: 4rem 1.5rem;
|
|
/* Viewport units, not `100%`. A percentage resolves against the
|
|
parent's HEIGHT, and on the 503 page the parent is <body>, whose
|
|
height is auto — so it resolved to zero, the panel was only as tall
|
|
as its text, and the bottom two thirds of the screen stayed the
|
|
browser's default white. dvh so a phone's collapsing address bar does
|
|
not leave a strip either; vh first for anything that lacks it. */
|
|
min-height: 100vh; min-height: 100dvh;
|
|
background: var(--cpu-bg); color: var(--cpu-fg);
|
|
font-family: "IBM Plex Sans", "Plex Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
|
|
}
|
|
.cpu-main { max-width: 34rem; width: 100%; }
|
|
.cpu-brand { display: flex; align-items: center; justify-content: center; gap: 0.625rem; }
|
|
.cpu-mark { width: 2.25rem; height: 2.25rem; flex: none; }
|
|
.cpu-word { font-size: 1.25rem; font-weight: 700; letter-spacing: -0.025em; color: var(--cpu-fg); }
|
|
.cpu-word span { font-weight: 600; color: var(--cpu-muted); }
|
|
.cpu-badge { display: inline-flex; align-items: center; gap: 0.5rem; margin-top: 1.5rem;
|
|
padding: 0.4rem 0.85rem; border: 1px solid #e8cdb2; border-radius: 9999px;
|
|
background: #fff3e9; color: var(--cpu-accent-ink);
|
|
font-family: "IBM Plex Mono", "Plex Mono", ui-monospace, monospace;
|
|
font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.07em; }
|
|
.cpu-dot { width: 0.375rem; height: 0.375rem; border-radius: 9999px; background: var(--cpu-accent);
|
|
animation: cpu-pulse 2s ease-in-out infinite; }
|
|
@keyframes cpu-pulse { 50% { opacity: 0.3; } }
|
|
@media (prefers-reduced-motion: reduce) { .cpu-dot { animation: none; } }
|
|
.cpu-title { margin: 1.5rem 0 0; font-size: clamp(1.6rem, 4vw, 2.1rem); font-weight: 700;
|
|
line-height: 1.1; letter-spacing: -0.035em; }
|
|
.cpu-body { margin: 0.9rem auto 0; max-width: 42ch; font-size: 0.95rem; line-height: 1.6;
|
|
color: var(--cpu-muted); }
|
|
.cpu-foot { margin-top: 2.25rem; font-family: "IBM Plex Mono", "Plex Mono", ui-monospace, monospace;
|
|
font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.07em; color: var(--cpu-muted); }
|
|
.cpu-stuck { margin-top: 1.5rem; padding-top: 1.25rem; border-top: 1px solid var(--cpu-line); }
|
|
.cpu-stuck p { margin: 0; font-size: 0.85rem; color: var(--cpu-muted); }
|
|
.cpu-dismiss { margin-top: 0.75rem; padding: 0.5rem 1rem; border: 1px solid var(--cpu-line);
|
|
border-radius: 0.6rem; background: var(--cpu-card); color: var(--cpu-fg);
|
|
font: inherit; font-size: 0.85rem; font-weight: 500; cursor: pointer; }
|
|
.cpu-dismiss:hover { background: var(--cpu-card-2); }
|
|
.cpu-detail { margin-top: 2rem; text-align: left; }
|
|
.cpu-step { margin: 0; padding: 0.8rem 1rem; border: 1px solid var(--cpu-line); border-radius: 0.7rem;
|
|
background: var(--cpu-card); font-size: 0.9rem; font-weight: 500; }
|
|
.cpu-since { margin: 0.55rem 0 0; font-size: 0.78rem; color: var(--cpu-muted); }
|
|
.cpu-log { margin: 0.55rem 0 0; max-height: 11rem; overflow: auto; padding: 0.8rem 1rem;
|
|
border: 1px solid var(--cpu-line); border-radius: 0.7rem; background: var(--cpu-card-2);
|
|
font-family: "IBM Plex Mono", "Plex Mono", ui-monospace, monospace;
|
|
font-size: 0.72rem; line-height: 1.6; color: var(--cpu-muted); }
|
|
</style>
|
|
|
|
<div class="cpu-wrap">
|
|
<main class="cpu-main">
|
|
{{-- The real mark, not a rounded orange tile. An empty brand square
|
|
reads as an image that failed to load, and this panel is shown at
|
|
the one moment somebody is already wondering whether something is
|
|
broken. --}}
|
|
<div class="cpu-brand"><svg class="cpu-mark" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<defs>
|
|
<linearGradient id="cpu-mark-grad" x1="0" y1="0" x2="64" y2="64" gradientUnits="userSpaceOnUse">
|
|
<stop stop-color="#FB923C"/><stop offset="0.55" stop-color="#F97316"/><stop offset="1" stop-color="#C2560A"/>
|
|
</linearGradient>
|
|
</defs>
|
|
<rect width="64" height="64" rx="15" fill="url(#cpu-mark-grad)"/>
|
|
<path d="M22 41h20a8 8 0 0 0 .7-15.97A11 11 0 0 0 21.4 27.2 8.5 8.5 0 0 0 22 44z" fill="#ffffff" fill-opacity="0.22"/>
|
|
<path d="M32 17 44 44l-12-6-12 6z" fill="#ffffff"/>
|
|
</svg>
|
|
<span class="cpu-word">CluPilot <span>Cloud</span></span>
|
|
</div>
|
|
|
|
<div class="cpu-badge"><span class="cpu-dot"></span>{{ __('updating.badge') }}</div>
|
|
<h1 class="cpu-title">{{ __('updating.title') }}</h1>
|
|
<p class="cpu-body">{{ $body ?? __('updating.body') }}</p>
|
|
|
|
{{-- Only in the console. A customer looking at the 503 has no use for a
|
|
deployment step, and an operator watching a run that is stuck has
|
|
no use for anything else: "läuft gerade" alone is what sends
|
|
somebody to the shell. The markup is Alpine-driven and inert
|
|
wherever Alpine is not — which is why it is behind a flag rather
|
|
than always present. --}}
|
|
@if ($detail ?? false)
|
|
<div class="cpu-detail">
|
|
<p class="cpu-step" x-show="!step">{{ $starting ?? '' }}</p>
|
|
<p class="cpu-step" x-show="step" x-text="step" x-cloak></p>
|
|
<p class="cpu-since" x-show="runningSince" x-text="runningSince" x-cloak></p>
|
|
<pre class="cpu-log" x-show="log" x-text="log" x-cloak></pre>
|
|
</div>
|
|
@endif
|
|
<p class="cpu-foot">{{ __('updating.auto') }}</p>
|
|
|
|
{{-- Only when the server has stopped answering for long enough that
|
|
this is no longer a restart. An always-present close button would
|
|
be a lie — the deployment does not stop because somebody dismissed
|
|
a panel — but being held behind one while the application is broken
|
|
is worse. --}}
|
|
@if ($detail ?? false)
|
|
<div class="cpu-stuck" x-show="stuck" x-cloak>
|
|
<p>{{ __('updating.stuck') }}</p>
|
|
<button type="button" class="cpu-dismiss" @click="dismiss()">{{ __('updating.dismiss') }}</button>
|
|
</div>
|
|
@endif
|
|
</main>
|
|
</div>
|