Files
werkator/src/main/resources/static/gittally.css
T
mhoennigandClaude Opus 5 0193386642 Show an unreachable origin in the web UI (step 19)
A watcher that cannot fetch leaves the server perfectly reachable and
every branch row stale — a calm list that implies nothing changed. The
state was already recorded and served at /api/watcher; only nothing
rendered it.

The banner lives in the shared nav fragment, so every view inherits it,
and is fed from the view's own polling tick without being chained to it.
It stays separate from live-indicator: that one reports whether the
browser reaches the server, this one whether the server reaches origin.

Also caps the log volume the outage exposed: a lasting fetch failure is
logged when its message changes, not on every cycle, and the recovery is
logged once. Same for an invalid atTimes slot.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-30 11:11:45 +02:00

185 lines
11 KiB
CSS

/* GitTally web UI — loosely ported from the legacy generated pages. */
:root {
color-scheme: light dark;
--bg: #f6f8fa;
--panel: #ffffff;
--text: #1f2937;
--muted: #6b7280;
--border: #d7dde5;
--row: #f9fafb;
--link: #155eef;
--success-bg: #dcfce7;
--success-text: #166534;
--failed-bg: #fee2e2;
--failed-text: #991b1b;
--running-bg: #dbeafe;
--running-text: #1d4ed8;
--pending-bg: #ede9fe;
--pending-text: #5b21b6;
--interrupted-bg: #ffedd5;
--interrupted-text: #9a3412;
--cancelled-bg: #e5e7eb;
--cancelled-text: #374151;
--unknown-bg: #f3f4f6;
--unknown-text: #4b5563;
}
@media (prefers-color-scheme: dark) {
:root {
--bg: #111827;
--panel: #1f2937;
--text: #f3f4f6;
--muted: #9ca3af;
--border: #374151;
--row: #182235;
--link: #93c5fd;
--success-bg: #12351f;
--success-text: #86efac;
--failed-bg: #3f1717;
--failed-text: #fca5a5;
--running-bg: #112c55;
--running-text: #93c5fd;
--pending-bg: #2e1065;
--pending-text: #c4b5fd;
--interrupted-bg: #431f0b;
--interrupted-text: #fdba74;
--cancelled-bg: #374151;
--cancelled-text: #d1d5db;
--unknown-bg: #374151;
--unknown-text: #d1d5db;
}
}
* { box-sizing: border-box; }
body { margin: 0; background: var(--bg); color: var(--text); font: 14px/1.45 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
main { width: min(1180px, calc(100% - 32px)); margin: 32px auto; }
h1 { display: flex; align-items: center; gap: 10px; margin: 0 0 18px; font-size: 28px; font-weight: 700; }
h1 img { width: 32px; height: 32px; flex: none; }
h1 .repo-name { color: var(--muted); font-size: 18px; font-weight: 400; align-self: flex-end; }
h2 { margin: 20px 0 10px; font-size: 18px; }
.title-home { display: inline-flex; flex: none; }
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }
a { color: var(--link); font-weight: 650; text-decoration: none; }
a:hover { text-decoration: underline; }
.muted { color: var(--muted); }
/* view toggle nav + live indicator */
.view-row { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 0 0 18px; }
.view-row-actions { margin-left: auto; display: inline-flex; align-items: center; gap: 8px; }
.reload-button { appearance: none; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--link); font: 18px/1 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; cursor: pointer; }
.reload-button:hover { background: color-mix(in srgb, var(--link) 8%, transparent); }
.reload-button.is-reloading { animation: reload-spin 0.6s ease-out; }
@keyframes reload-spin { to { transform: rotate(360deg); } }
.view-toggle { display: inline-flex; gap: 0; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; background: var(--panel); }
.view-toggle a, .view-toggle span { display: inline-flex; min-width: 88px; justify-content: center; padding: 7px 12px; font-weight: 700; }
.view-toggle span { background: var(--link); color: white; }
.view-toggle a { color: var(--link); }
.view-toggle a:hover { background: color-mix(in srgb, var(--link) 8%, transparent); text-decoration: none; }
/* build tables */
.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); box-shadow: 0 12px 28px rgb(15 23 42 / 0.08); }
table { width: 100%; border-collapse: collapse; min-width: 900px; }
th, td { padding: 12px 14px; text-align: left; vertical-align: middle; border-bottom: 1px solid var(--border); }
th { position: sticky; top: 0; background: var(--panel); color: var(--muted); font-size: 12px; font-weight: 700; text-transform: uppercase; }
tbody tr:nth-child(even) { background: var(--row); }
tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover { background: color-mix(in srgb, var(--link) 8%, transparent); }
tbody.is-stale { opacity: 0.55; }
/* Server-side staleness: the watcher cannot reach origin. Distinct from the live
indicator, which reports whether this browser reaches the server. */
.watcher-banner { display: flex; flex-wrap: wrap; align-items: baseline; gap: 10px; margin: -8px 0 18px; padding: 9px 12px; border-radius: 8px; background: var(--failed-bg); color: var(--failed-text); font-size: 13px; }
.watcher-banner strong { text-transform: uppercase; letter-spacing: 0.03em; font-size: 12px; }
.watcher-banner[hidden] { display: none; }
.branch { font-weight: 650; }
.duration-cell { white-space: nowrap; }
/* queue wait time of a pending build — italic to distinguish it from real build time */
.duration-wait { font-style: italic; color: var(--muted); }
.empty { padding: 28px 14px; color: var(--muted); text-align: center; }
/* system metrics */
#system-table { min-width: 640px; }
.num { text-align: right; font-variant-numeric: tabular-nums; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; }
/* critical utilization of the current value: warn from 80% of the total, critical from 90% */
.metric-warn { background: var(--interrupted-bg); color: var(--interrupted-text); font-weight: 700; }
.metric-crit { background: var(--failed-bg); color: var(--failed-text); font-weight: 700; }
th.num { font-family: inherit; font-size: 12px; }
.meta { margin: 14px 0 0; color: var(--muted); font-size: 13px; display: flex; justify-content: space-between; align-items: baseline; flex-wrap: wrap; gap: 6px; }
/* status badges */
.status { display: inline-flex; align-items: center; min-width: 72px; justify-content: center; padding: 3px 10px; border-radius: 999px; font-size: 12px; font-weight: 700; text-transform: uppercase; }
.status-success { background: var(--success-bg); color: var(--success-text); }
.status-failed, .status-error { background: var(--failed-bg); color: var(--failed-text); }
.status-running { background: var(--running-bg); color: var(--running-text); }
.status-pending { background: var(--pending-bg); color: var(--pending-text); }
.status-interrupted { background: var(--interrupted-bg); color: var(--interrupted-text); }
.status-cancelled { background: var(--cancelled-bg); color: var(--cancelled-text); }
.status-unknown, .status-finished { background: var(--unknown-bg); color: var(--unknown-text); }
/* copy buttons and links with tools */
.link-tools { display: inline-flex; align-items: center; gap: 5px; max-width: 100%; }
.copy-button { appearance: none; display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; border: 1px solid transparent; border-radius: 5px; background: transparent; color: var(--muted); font: 14px/1 system-ui, sans-serif; cursor: pointer; }
.copy-button:hover { border-color: var(--border); background: color-mix(in srgb, var(--link) 8%, transparent); color: var(--link); }
.copy-button.is-copied { color: var(--success-text); }
.artifact-link { display: inline-flex; align-items: center; justify-content: center; }
/* row actions */
.actions-column, .actions-cell { width: 96px; min-width: 96px; }
.actions { display: inline-flex; align-items: center; justify-content: center; gap: 6px; }
.action-button { appearance: none; display: inline-flex; align-items: center; justify-content: center; min-width: 30px; height: 30px; padding: 0 6px; border: 1px solid var(--border); border-radius: 6px; background: var(--panel); color: var(--link); font: 18px/1 system-ui, sans-serif; cursor: pointer; }
.action-button:hover { background: color-mix(in srgb, var(--link) 8%, transparent); }
.action-button:disabled { color: var(--muted); cursor: default; }
.delete-button, .cancel-button { color: var(--failed-text); }
.cancel-button { font-size: 13px; font-weight: 700; }
/* current-build cards */
.empty-panel { padding: 28px 14px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); color: var(--muted); text-align: center; }
.build-card { margin: 0 0 18px; border: 1px solid var(--border); border-radius: 8px; background: var(--panel); box-shadow: 0 12px 28px rgb(15 23 42 / 0.08); overflow: hidden; }
.build-card-header { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--border); }
.build-card-actions { margin-left: auto; }
.build-card-result { padding: 10px 14px; border-bottom: 1px solid var(--border); }
.live-log { margin: 0; padding: 12px 14px; max-height: 420px; overflow: auto; background: var(--bg); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
/* artifact index */
.panel { border: 1px solid var(--border); border-radius: 8px; background: var(--panel); padding: 8px 22px 22px; box-shadow: 0 12px 28px rgb(15 23 42 / 0.08); }
.build-facts { list-style: none; margin: 0; padding: 0; }
.build-facts li { margin: 0 0 8px; }
/* footer */
.site-footer { width: min(1180px, calc(100% - 32px)); margin: 24px auto 32px; color: var(--muted); font-size: 12px; }
/* small screens: stack table rows as cards, like legacy */
@media (max-width: 680px) {
main { margin: 16px auto; }
/* title on the first line, repo name below it, logo left of both */
h1 {
display: grid;
grid-template-columns: auto minmax(0, 1fr);
column-gap: 10px;
row-gap: 1px;
font-size: 22px;
}
h1 .title-home { grid-column: 1; grid-row: 1 / span 2; align-self: center; }
h1 .title-text { grid-column: 2; grid-row: 1; align-self: end; }
h1 .repo-name { grid-column: 2; grid-row: 2; align-self: start; font-size: 15px; overflow-wrap: anywhere; }
.view-row { flex-wrap: wrap; }
.view-toggle a, .view-toggle span { min-width: 0; padding: 6px 9px; font-size: 13px; }
/* the live indicator collapses to a colored state dot so it cannot squeeze the menu;
its status color (green/red/grey) carries the live/error/static state */
#live-indicator { min-width: 14px; width: 14px; height: 14px; padding: 0; border-radius: 50%; font-size: 0; }
.table-wrap { overflow-x: visible; border: none; border-radius: 0; background: transparent; box-shadow: none; }
table, thead, tbody, tr, td { display: block; }
table { min-width: 0; }
thead { display: none; }
tbody { display: flex; flex-direction: column; gap: 12px; }
tbody tr { border: 1px solid var(--border); border-radius: 10px; background: var(--panel); overflow: hidden; box-shadow: 0 2px 8px rgb(15 23 42 / 0.07); }
tbody tr:nth-child(even) { background: var(--panel); }
td { display: flex; align-items: center; gap: 10px; padding: 10px 14px; }
td + td { border-top: 1px solid color-mix(in srgb, var(--border) 50%, transparent); }
td[data-label]::before { content: attr(data-label); width: 90px; flex-shrink: 0; font-size: 11px; font-weight: 700; text-transform: uppercase; color: var(--muted); }
.actions-cell { justify-content: center; background: color-mix(in srgb, var(--border) 18%, transparent); width: auto; }
#system-table { min-width: 0; }
.num { text-align: left; }
}