Files
josh 1317ff6369
build-and-push / test (push) Successful in 34s
build-and-push / build-and-push (push) Successful in 1m8s
Add job detail page with activity log and cancel support
Operations are now clickable from the host detail page, linking to
/ops/{id} which shows the operation info, host link, duration, and
activity log filtered to that operation. Active operations can be
cancelled, which transitions the host to failed and releases the lock.
SSE activity events now include operation_id for real-time filtering.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-14 10:37:18 -04:00

453 lines
10 KiB
CSS

/* === RESET === */
* { box-sizing: border-box; margin: 0; padding: 0; }
/* === CUSTOM PROPERTIES === */
:root {
--bg: #f0f2f5;
--card: #ffffff;
--nav: #0c1222;
--nav-text: #94a3b8;
--nav-text-hover: #ffffff;
--border: #e0e4ea;
--border-focus: #2563eb;
--text: #1a2233;
--text-secondary: #5f6d7e;
--text-tertiary: #94a3b8;
--accent: #2563eb;
--accent-hover: #1d4fd8;
--accent-subtle: #eff4ff;
--green: #16a34a;
--green-bg: #f0fdf4;
--green-border: #bbf7d0;
--amber: #d97706;
--amber-bg: #fffbeb;
--amber-border: #fde68a;
--red: #dc2626;
--red-bg: #fef2f2;
--red-border: #fecaca;
--blue: #2563eb;
--blue-bg: #eff6ff;
--blue-border: #bfdbfe;
--grey: #6b7280;
--grey-bg: #f3f4f6;
--grey-border: #e5e7eb;
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-md: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
--shadow-lg: 0 4px 12px rgba(0, 0, 0, 0.06), 0 1px 3px rgba(0, 0, 0, 0.04);
--radius: 8px;
--radius-sm: 6px;
--font: "Outfit", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
--font-mono: "IBM Plex Mono", "SF Mono", "Consolas", monospace;
}
/* === GLOBAL === */
body {
font-family: var(--font);
background: var(--bg);
color: var(--text);
line-height: 1.5;
font-size: 0.95rem;
-webkit-font-smoothing: antialiased;
}
h2 {
font-weight: 600;
font-size: 1.35rem;
color: var(--text);
margin-bottom: 1.5rem;
letter-spacing: -0.01em;
}
h3 {
font-weight: 600;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.06em;
color: var(--text-tertiary);
margin: 2rem 0 0.75rem;
}
/* === TOPBAR === */
.topbar {
display: flex;
align-items: center;
gap: 2rem;
padding: 0 1.5rem;
height: 60px;
background: var(--nav);
}
.brand {
font-weight: 700;
font-size: 1.05rem;
color: #ffffff;
text-decoration: none;
letter-spacing: -0.01em;
}
.nav-links { display: flex; gap: 0.25rem; }
.nav-links a {
color: var(--nav-text);
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
padding: 0.375rem 0.75rem;
border-radius: var(--radius-sm);
transition: color 0.15s, background 0.15s;
}
.nav-links a:hover {
color: var(--nav-text-hover);
background: rgba(255, 255, 255, 0.08);
}
.sse-status {
margin-left: auto;
display: flex;
align-items: center;
gap: 0.375rem;
}
.sse-label {
font-size: 0.7rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--nav-text);
}
/* === LED === */
.led {
display: inline-block;
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
flex-shrink: 0;
}
.led-green { color: var(--green); }
.led-amber { color: var(--amber); animation: pulse 2s ease-in-out infinite; }
.led-red { color: var(--red); animation: pulse 1.5s ease-in-out infinite; }
.led-blue { color: var(--blue); animation: pulse 2.5s ease-in-out infinite; }
.led-grey { color: var(--grey); }
.led-lg { width: 9px; height: 9px; }
/* Navbar LED uses lighter colors */
.topbar .led-green { color: #4ade80; }
.topbar .led-red { color: #f87171; }
.topbar .led-grey { color: var(--nav-text); }
/* === LAYOUT === */
main {
padding: 2rem;
max-width: 1200px;
margin: 0 auto;
}
/* === BUTTONS === */
.btn {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0.5rem 1.1rem;
background: var(--accent);
color: #ffffff;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
text-decoration: none;
font-size: 0.875rem;
font-family: var(--font);
font-weight: 500;
transition: background 0.15s, box-shadow 0.15s;
box-shadow: var(--shadow-sm);
}
.btn:hover {
background: var(--accent-hover);
box-shadow: var(--shadow-md);
}
.btn-danger {
background: var(--red);
}
.btn-danger:hover {
background: #b91c1c;
}
.btn-sm {
font-size: 0.75rem;
padding: 0.3rem 0.6rem;
}
/* === ACTIONS === */
.actions {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.count {
color: var(--text-tertiary);
font-size: 0.8rem;
font-weight: 500;
}
/* === HOST GRID === */
.host-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 1rem;
}
.tile {
display: block;
padding: 1.25rem 1.5rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
text-decoration: none;
color: var(--text);
transition: border-color 0.15s, box-shadow 0.2s;
box-shadow: var(--shadow-sm);
}
.tile:hover {
border-color: var(--accent);
box-shadow: var(--shadow-lg);
}
.tile-header {
display: flex;
align-items: center;
gap: 0.625rem;
margin-bottom: 0.5rem;
}
.tile-name {
font-weight: 600;
font-size: 1rem;
color: var(--text);
font-family: var(--font-mono);
}
.tile-meta { margin-bottom: 0.75rem; }
.tile-type {
font-size: 0.85rem;
color: var(--text-secondary);
}
.tile-footer {
display: flex;
align-items: center;
justify-content: space-between;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
}
.tile-mac {
font-size: 0.8rem;
font-family: var(--font-mono);
color: var(--text-tertiary);
}
/* === STATUS BADGES === */
.tile-state-label, .badge {
display: inline-flex;
align-items: center;
gap: 0.375rem;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
padding: 0.2rem 0.5rem;
border-radius: 4px;
}
.state-grey .tile-state-label, .badge.state-grey { background: var(--grey-bg); color: var(--grey); border: 1px solid var(--grey-border); }
.state-blue .tile-state-label, .badge.state-blue { background: var(--blue-bg); color: var(--blue); border: 1px solid var(--blue-border); }
.state-amber .tile-state-label, .badge.state-amber { background: var(--amber-bg); color: var(--amber); border: 1px solid var(--amber-border); }
.state-green .tile-state-label, .badge.state-green { background: var(--green-bg); color: var(--green); border: 1px solid var(--green-border); }
.state-red .tile-state-label, .badge.state-red { background: var(--red-bg); color: var(--red); border: 1px solid var(--red-border); }
/* === PANELS === */
.panel {
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.5rem;
box-shadow: var(--shadow-sm);
margin-bottom: 1rem;
}
/* === HOST DETAIL === */
.host-header {
display: flex;
align-items: center;
gap: 0.75rem;
margin-bottom: 1.5rem;
}
.host-header h2 { margin-bottom: 0; }
/* === TABLES === */
.detail-table { margin-bottom: 1.5rem; }
.detail-table th {
text-align: left;
padding: 0.5rem 1.5rem 0.5rem 0;
color: var(--text-tertiary);
font-weight: 500;
font-size: 0.825rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.detail-table td {
padding: 0.5rem 0;
color: var(--text);
font-family: var(--font-mono);
font-size: 0.9rem;
}
.ops-table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.ops-table th {
text-align: left;
padding: 0.625rem 0.75rem;
border-bottom: 2px solid var(--border);
color: var(--text-tertiary);
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.04em;
}
.ops-table td {
padding: 0.625rem 0.75rem;
border-bottom: 1px solid var(--border);
color: var(--text);
}
.ops-table tr:hover td { background: var(--accent-subtle); }
.ops-table a { color: var(--accent); text-decoration: none; font-weight: 500; }
.ops-table a:hover { text-decoration: underline; }
/* === FORMS === */
.form { max-width: 420px; }
.form label {
display: block;
margin-bottom: 1.25rem;
color: var(--text-secondary);
font-size: 0.85rem;
font-weight: 500;
}
.form input, .form select, .form textarea {
display: block;
width: 100%;
padding: 0.55rem 0.75rem;
margin-top: 0.375rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
color: var(--text);
font-family: var(--font-mono);
font-size: 0.9rem;
transition: border-color 0.15s, box-shadow 0.15s;
}
.form input:focus, .form select:focus, .form textarea:focus {
outline: none;
border-color: var(--border-focus);
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.form select { font-family: var(--font); }
.form textarea { min-height: 80px; resize: vertical; }
.form .btn { margin-top: 0.5rem; }
/* === ERROR === */
.error {
background: var(--red-bg);
color: var(--red);
padding: 0.75rem 1rem;
border: 1px solid var(--red-border);
border-radius: var(--radius-sm);
margin-bottom: 1rem;
font-size: 0.825rem;
font-weight: 500;
}
/* === EMPTY STATE === */
.empty {
color: var(--text-tertiary);
padding: 3rem;
text-align: center;
}
.empty a { color: var(--accent); text-decoration: none; font-weight: 500; }
.empty a:hover { text-decoration: underline; }
/* === UPLOAD PROGRESS === */
.upload-progress { max-width: 420px; }
.progress-bar-track {
width: 100%;
height: 6px;
background: var(--bg);
border-radius: 3px;
margin: 0.75rem 0;
overflow: hidden;
}
.progress-bar-fill {
height: 100%;
width: 0%;
background: var(--accent);
border-radius: 3px;
transition: width 0.3s ease;
}
.progress-bar-fill.complete { background: var(--green); }
.progress-text { font-size: 0.825rem; color: var(--text); font-weight: 500; }
.progress-detail { font-size: 0.775rem; color: var(--text-secondary); }
/* === STUCK WARNING === */
.stuck-warning {
background: var(--amber-bg);
color: var(--amber);
border: 1px solid var(--amber-border);
padding: 0.75rem 1rem;
border-radius: var(--radius-sm);
margin-bottom: 1rem;
font-size: 0.825rem;
font-weight: 500;
line-height: 1.5;
}
/* === ACTIVITY LOG === */
.activity-log { max-height: 400px; overflow-y: auto; }
.log-entry {
display: flex;
align-items: baseline;
gap: 0.75rem;
padding: 0.4rem 0.75rem;
border-bottom: 1px solid var(--border);
font-size: 0.825rem;
}
.log-entry:last-child { border-bottom: none; }
.log-time {
color: var(--text-tertiary);
font-family: var(--font-mono);
font-size: 0.75rem;
min-width: 3.5rem;
flex-shrink: 0;
}
.log-source {
color: var(--text-tertiary);
font-family: var(--font-mono);
font-size: 0.75rem;
min-width: 5rem;
flex-shrink: 0;
}
.log-msg { color: var(--text); }
.log-warn .log-msg { color: var(--amber); }
.log-error .log-msg { color: var(--red); font-weight: 500; }
/* === UTILITY === */
.inline { display: inline; }
/* === ANIMATIONS === */
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.4; }
}
/* === RESPONSIVE === */
@media (max-width: 720px) {
.host-grid { grid-template-columns: 1fr; }
main { padding: 1.25rem; }
.topbar { padding: 0 1rem; gap: 1rem; }
}