<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Content-Security-Policy"
content="default-src 'none';
script-src 'unsafe-inline';
style-src 'unsafe-inline';
img-src data: blob:;">
<title>{{TITLE}}</title>
<style>
{{CSS}}
/* Covenant-specific styles */
.covenant-header {
margin-bottom: var(--daemon-space-lg);
}
.covenant-header__title {
display: flex;
align-items: center;
gap: var(--daemon-space-md);
margin-bottom: var(--daemon-space-sm);
}
.covenant-header__status {
display: flex;
align-items: center;
gap: var(--daemon-space-sm);
color: var(--daemon-text-muted);
font-size: 0.875rem;
}
.covenant-diagram-container {
background-color: var(--daemon-surface);
border: var(--daemon-border);
border-radius: var(--daemon-radius);
padding: var(--daemon-space-lg);
margin-bottom: var(--daemon-space-lg);
}
.covenant-phase-info {
background-color: var(--daemon-bg-alt);
border-left: 3px solid var(--daemon-accent);
padding: var(--daemon-space-md);
margin-top: var(--daemon-space-lg);
font-size: 0.875rem;
line-height: 1.6;
}
.covenant-phase-info__label {
font-weight: 600;
color: var(--daemon-accent);
text-transform: uppercase;
font-size: 0.75rem;
letter-spacing: 0.05em;
margin-bottom: var(--daemon-space-xs);
}
.covenant-phase-info__description {
color: var(--daemon-text);
}
</style>
</head>
<body>
<!-- Real-time update notification badge -->
<div id="update-indicator" class="daemon-update-badge" role="status" aria-live="polite">
<span class="daemon-update-badge__dot"></span>
<span>New data available</span>
<button id="refresh-btn" class="daemon-btn daemon-btn--small">Refresh</button>
</div>
<div id="app">
<div class="daemon-container" style="padding: var(--daemon-space-lg);">
<!-- Header Section -->
<header class="covenant-header">
<div class="covenant-header__title">
<h1>Covenant Status</h1>
<span class="covenant-header__status">
{{STATUS}}
</span>
</div>
</header>
<!-- State Machine Diagram -->
<div class="covenant-diagram-container">
<svg viewBox="0 0 720 160" class="covenant-diagram" xmlns="http://www.w3.org/2000/svg">
<!-- Arrow marker definition -->
<defs>
<marker id="arrowhead" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="var(--daemon-text-muted)" />
</marker>
</defs>
<!-- States -->
<g class="covenant-state" data-phase="commune" transform="translate(80,80)">
<circle r="45" />
<text y="5">COMMUNE</text>
</g>
<g class="covenant-state" data-phase="counsel" transform="translate(260,80)">
<circle r="45" />
<text y="5">COUNSEL</text>
</g>
<g class="covenant-state" data-phase="inscribe" transform="translate(440,80)">
<circle r="45" />
<text y="5">INSCRIBE</text>
</g>
<g class="covenant-state" data-phase="seal" transform="translate(620,80)">
<circle r="45" />
<text y="5">SEAL</text>
</g>
<!-- Transition arrows -->
<path class="covenant-transition" d="M125,80 L215,80" marker-end="url(#arrowhead)" />
<path class="covenant-transition" d="M305,80 L395,80" marker-end="url(#arrowhead)" />
<path class="covenant-transition" d="M485,80 L575,80" marker-end="url(#arrowhead)" />
</svg>
</div>
<!-- Token Status Panel -->
<div class="token-status" id="token-status">
<div class="token-status__header">
<span class="token-status__label">Preflight Token</span>
<span class="token-status__badge token-status__badge--{{TOKEN_STATUS}}" id="token-badge">{{TOKEN_STATUS_LABEL}}</span>
</div>
<div class="token-status__countdown" id="countdown-panel" style="{{COUNTDOWN_VISIBILITY}}">
<span class="token-status__icon">⏱</span>
<span class="token-status__time" id="countdown">{{TOKEN_REMAINING}}</span>
<span class="token-status__unit">remaining</span>
</div>
<button class="daemon-btn daemon-btn--small" data-action="context-check" id="refresh-token-btn">
Refresh Token
</button>
</div>
<!-- Current Phase Info -->
{{PHASE_INFO}}
</div>
</div>
<script>
{{SCRIPT}}
// Covenant phase management
(function() {
/**
* Set the current active phase in the state machine diagram.
* @param {string} phase - The phase identifier (commune, counsel, inscribe, seal)
*/
function setCurrentPhase(phase) {
// Remove active class from all states
var states = document.querySelectorAll('.covenant-state');
states.forEach(function(state) {
state.classList.remove('covenant-state--active');
});
// Add active class to the matching phase
var targetState = document.querySelector('[data-phase="' + phase + '"]');
if (targetState) {
// Check if D3 is available for smooth transition
if (window.d3) {
d3.select(targetState)
.classed('covenant-state--active', true)
.select('circle')
.transition()
.duration(500)
.ease(d3.easeCubicOut)
.attr('r', 48)
.transition()
.duration(300)
.attr('r', 45);
} else {
// Fallback: just add the class
targetState.classList.add('covenant-state--active');
}
}
}
// Make setCurrentPhase available globally
window.setCurrentPhase = setCurrentPhase;
// Set initial phase on load
document.addEventListener('DOMContentLoaded', function() {
setCurrentPhase('{{CURRENT_PHASE}}');
});
})();
// Token countdown timer using Date.now() for accuracy
(function() {
var countdownInterval = null;
var targetTime = null;
var WARNING_THRESHOLD = 60; // seconds
function startCountdown(expiresAtIso) {
if (!expiresAtIso) return;
targetTime = new Date(expiresAtIso).getTime();
if (countdownInterval) clearInterval(countdownInterval);
countdownInterval = setInterval(updateCountdown, 1000);
updateCountdown(); // Initial update
}
function updateCountdown() {
var remaining = Math.max(0, Math.floor((targetTime - Date.now()) / 1000));
var countdownEl = document.getElementById('countdown');
var timeEl = document.querySelector('.token-status__time');
var badgeEl = document.getElementById('token-badge');
if (countdownEl) {
countdownEl.textContent = formatTime(remaining);
}
// Warning state when < 60s
if (timeEl && remaining <= WARNING_THRESHOLD && remaining > 0) {
timeEl.classList.add('token-status__time--warning');
}
// Expired state
if (remaining === 0) {
clearInterval(countdownInterval);
if (badgeEl) {
badgeEl.className = 'token-status__badge token-status__badge--expired';
badgeEl.textContent = 'EXPIRED';
}
if (timeEl) {
timeEl.classList.add('token-status__time--warning');
}
}
}
function formatTime(seconds) {
var m = Math.floor(seconds / 60);
var s = seconds % 60;
return m + ':' + s.toString().padStart(2, '0');
}
// Initialize countdown if token is valid
var expiresAt = '{{TOKEN_EXPIRES_AT}}';
if (expiresAt && expiresAt !== 'null' && expiresAt !== '' && expiresAt !== 'None') {
startCountdown(expiresAt);
}
// Make startCountdown available globally for dynamic updates
window.startCountdown = startCountdown;
})();
// Context check button handler for token refresh
(function() {
var btn = document.querySelector('[data-action="context-check"]');
if (btn) {
btn.addEventListener('click', function() {
if (window.SecureMessenger) {
SecureMessenger.send('tool_request', {
tool: 'context_check',
args: { description: 'Refreshing token from Covenant Status dashboard' }
});
}
});
}
})();
// Real-time update notification receiver
(function() {
var updateTimeout = null;
var DEBOUNCE_MS = 300;
var indicator = document.getElementById('update-indicator');
var refreshBtn = document.getElementById('refresh-btn');
if (window.SecureMessenger) {
SecureMessenger.on('data_updated', function(data) {
clearTimeout(updateTimeout);
updateTimeout = setTimeout(function() {
if (indicator) {
indicator.classList.add('daemon-update-badge--visible');
indicator.setAttribute('data-last-update', data.last_update || '');
}
}, DEBOUNCE_MS);
});
}
if (refreshBtn) {
refreshBtn.addEventListener('click', function() {
if (indicator) {
indicator.classList.remove('daemon-update-badge--visible');
}
if (window.SecureMessenger) {
SecureMessenger.send('tool_request', { tool: 'refresh_ui' });
}
});
}
})();
</script>
</body>
</html>