|
161 | 161 | display: flex; |
162 | 162 | align-items: center; |
163 | 163 | gap: 0.75rem; |
164 | | - margin-bottom: 0.5rem; |
| 164 | + margin-bottom: 0.25rem; |
| 165 | + } |
| 166 | + .agent-meta-row { |
| 167 | + display: flex; |
| 168 | + align-items: center; |
| 169 | + gap: 0.75rem; |
| 170 | + margin-bottom: 0.4rem; |
165 | 171 | flex-wrap: wrap; |
166 | 172 | } |
167 | 173 | .agent-name { |
|
223 | 229 |
|
224 | 230 | /* ── Gate Banner ── */ |
225 | 231 | .gate-banner { |
226 | | - background: rgba(210, 153, 34, 0.08); |
227 | | - border: 1px solid rgba(210, 153, 34, 0.25); |
228 | | - border-radius: 0; |
229 | | - padding: 0.4rem 0.6rem; |
230 | 232 | display: flex; |
231 | 233 | align-items: center; |
232 | 234 | gap: 0.4rem; |
233 | | - font-size: 0.7rem; |
| 235 | + font-size: 0.65rem; |
| 236 | + font-family: 'SF Mono', 'Fira Code', monospace; |
234 | 237 | margin-bottom: 0.4rem; |
235 | 238 | } |
236 | 239 | .gate-banner .gate-text { color: #d29922; font-weight: 500; } |
@@ -408,7 +411,6 @@ <h2>Activity</h2> |
408 | 411 |
|
409 | 412 | if (target === config.prodBranch || target === 'main' || target === 'master') return 'production'; |
410 | 413 | if (target === config.stagingBranch || target === 'staging') return 'staging'; |
411 | | - if (branch === config.prodBranch || branch === 'main' || branch === 'master') return 'production'; |
412 | 414 | if (branch === config.stagingBranch || branch === 'staging') return 'staging'; |
413 | 415 | return 'feature'; |
414 | 416 | } |
@@ -465,11 +467,12 @@ <h2>Activity</h2> |
465 | 467 |
|
466 | 468 | // Header summary |
467 | 469 | const activeCount = agents.filter(a => !a.stale).length; |
468 | | - const warningCount = 0; // Future: count agents without DeployNOPE active |
| 470 | + const gateCount = agents.filter(a => !a.stale && (a.deploynope || {}).gate && a.deploynope.gate.waiting).length; |
| 471 | + const warningCount = gateCount; |
469 | 472 | const dot = document.getElementById('connection-dot'); |
470 | 473 | dot.className = 'status-dot' + (warningCount > 0 ? ' warning' : ''); |
471 | 474 | document.getElementById('header-summary').textContent = |
472 | | - (warningCount > 0 ? warningCount + ' warning' + (warningCount > 1 ? 's' : '') + ' \u00b7 ' : '') + |
| 475 | + (gateCount > 0 ? gateCount + ' gate' + (gateCount > 1 ? 's' : '') + ' waiting \u00b7 ' : '') + |
473 | 476 | activeCount + ' agent' + (activeCount !== 1 ? 's' : ''); |
474 | 477 |
|
475 | 478 | // Build a set of repos that have staging claimed (per-repo staging lock) |
@@ -498,25 +501,47 @@ <h2>Activity</h2> |
498 | 501 | const stage = inferStage(agent); |
499 | 502 | const name = inferName(agent); |
500 | 503 | const target = agent.target || ''; |
501 | | - const staleClass = agent.stale ? ' stale' : ''; |
502 | | - const tagClass = agent.stale ? 'idle' : stage; |
503 | | - const tagText = agent.stale ? 'STALE' : stageLabel(agent); |
| 504 | + const gate = (agent.deploynope || {}).gate; |
| 505 | + const gateWaiting = gate && gate.waiting; |
| 506 | + const hasGate = (agent.deploynope || {}).gate && agent.deploynope.gate.waiting; |
| 507 | + const treatAsStale = agent.stale && !hasGate; |
| 508 | + const staleClass = treatAsStale ? ' stale' : ''; |
| 509 | + const tagClass = treatAsStale ? 'idle' : stage; |
| 510 | + const tagText = treatAsStale ? 'STALE' : stageLabel(agent); |
| 511 | + |
| 512 | + const isStaging = agent.repo && stagingByRepo.hasOwnProperty(agent.repo) && stagingByRepo[agent.repo] === agent.id; |
| 513 | + const lockIcon = isStaging ? '\uD83D\uDD12 ' : ''; |
504 | 514 |
|
505 | | - let inner = '<div class="agent-header">'; |
506 | | - inner += '<span class="agent-name">' + escapeHtml(name) + '</span>'; |
507 | 515 | var ghIcon = '<svg class="meta-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/></svg>'; |
508 | 516 | var branchIcon = '<svg class="meta-icon" viewBox="0 0 16 16" fill="currentColor"><path d="M11.75 2.5a.75.75 0 100 1.5.75.75 0 000-1.5zm-2.25.75a2.25 2.25 0 113 2.122V6A2.5 2.5 0 0110 8.5H6a1 1 0 00-1 1v1.128a2.251 2.251 0 11-1.5 0V5.372a2.25 2.25 0 111.5 0v1.836A2.492 2.492 0 016 7h4a1 1 0 001-1v-.628A2.25 2.25 0 019.5 3.25zM4.25 12a.75.75 0 100 1.5.75.75 0 000-1.5zM3.5 3.25a.75.75 0 111.5 0 .75.75 0 01-1.5 0z"/></svg>'; |
| 517 | + |
| 518 | + let inner = '<div class="agent-header">'; |
| 519 | + inner += '<span class="agent-name">' + escapeHtml(name) + '</span>'; |
| 520 | + inner += '<span class="agent-tag ' + tagClass + '">' + lockIcon + tagText + '</span>'; |
| 521 | + inner += '</div>'; |
| 522 | + |
| 523 | + inner += '<div class="agent-meta-row">'; |
509 | 524 | inner += '<span class="agent-meta">' + ghIcon + escapeHtml(agent.repo || '') + '</span>'; |
510 | 525 | if (agent.branch && target) { |
511 | 526 | inner += '<span class="agent-meta">' + branchIcon + escapeHtml(agent.branch) + ' <span class="agent-branch-arrow">→</span> ' + escapeHtml(target) + '</span>'; |
512 | 527 | } else if (agent.branch) { |
513 | 528 | inner += '<span class="agent-meta">' + branchIcon + escapeHtml(agent.branch) + '</span>'; |
514 | 529 | } |
515 | | - const isStaging = agent.repo && stagingByRepo.hasOwnProperty(agent.repo) && stagingByRepo[agent.repo] === agent.id; |
516 | | - const lockIcon = isStaging ? '\uD83D\uDD12 ' : ''; |
517 | | - inner += '<span class="agent-tag ' + tagClass + '">' + lockIcon + tagText + '</span>'; |
| 530 | + var prodBranch = (agent.config || {}).prodBranch || 'main'; |
| 531 | + var onProd = (agent.branch === prodBranch || agent.branch === 'main' || agent.branch === 'master'); |
| 532 | + if (onProd && stage !== 'production') { |
| 533 | + inner += '<span class="agent-meta" style="color:#f85149;">checked out on ' + escapeHtml(agent.branch) + '</span>'; |
| 534 | + } |
518 | 535 | inner += '</div>'; |
519 | 536 |
|
| 537 | + // Gate banner |
| 538 | + if (gateWaiting) { |
| 539 | + inner += '<div class="gate-banner">' + |
| 540 | + '<span class="gate-text">' + escapeHtml(gate.label || 'Waiting for sign-off') + '</span>' + |
| 541 | + (gate.since ? '<span style="margin-left:auto;font-size:0.6rem;color:#8b949e;">' + timeAgo(gate.since) + '</span>' : '') + |
| 542 | + '</div>'; |
| 543 | + } |
| 544 | + |
520 | 545 | // Last action |
521 | 546 | if (agent.lastAction) { |
522 | 547 | inner += '<div class="last-action"><code>' + escapeHtml(shortCommand(agent.lastAction.command || '')) + '</code> ' + timeAgo(agent.lastAction.timestamp) + '</div>'; |
|
0 commit comments