js constant

String const js

JavaScript code

Implementation

static const String js = r'''let allEnrollments = [];
let eventSource = null;
let autoApprovalRefreshTimer = null;

function switchTab(tabId) {
  const tabs = ['api-v4-config', 'api-v4-console', 'api-v4-transactions', 'activation', 'apkam-management'];
  const isApiV4Tab = tabId.startsWith('api-v4');
  const apiV4Trigger = document.getElementById('tab-btn-api-v4');

  tabs.forEach((tab) => {
      const button = document.getElementById(`tab-btn-${tab}`);
      const panel = document.getElementById(`tab-${tab}`);
      const isActive = tab === tabId;

      if (button) {
          button.classList.toggle('active', isActive);
          button.setAttribute('aria-selected', isActive ? 'true' : 'false');
      }

      if (panel) {
          panel.classList.toggle('active', isActive);
          panel.hidden = !isActive;
      }
  });

  if (apiV4Trigger) {
      apiV4Trigger.classList.toggle('active', isApiV4Tab);
      apiV4Trigger.setAttribute('aria-expanded', 'false');
  }

  closeApiV4Menu();

  if (tabId !== 'apkam-management') {
      closeWizardsMenu();
  }

  if (tabId === 'api-v4-transactions' && window.loadApiV4Transactions) {
      window.loadApiV4Transactions();
  }

  if (tabId === 'activation' && window.loadActivationCramKeys) {
      window.loadActivationCramKeys();
  }
}

function toggleApiV4Menu(event) {
  event.preventDefault();
  event.stopPropagation();

  const menu = document.getElementById('tab-menu-api-v4');
  const trigger = document.getElementById('tab-btn-api-v4');
  if (!menu || !trigger) return;

  const shouldOpen = menu.hidden;
  menu.hidden = !shouldOpen;
  trigger.setAttribute('aria-expanded', shouldOpen ? 'true' : 'false');
}

function closeApiV4Menu() {
  const menu = document.getElementById('tab-menu-api-v4');
  const trigger = document.getElementById('tab-btn-api-v4');
  if (menu) {
      menu.hidden = true;
  }
  if (trigger) {
      trigger.setAttribute('aria-expanded', 'false');
  }
}

document.addEventListener('click', function (event) {
  const menu = document.getElementById('tab-menu-api-v4');
  const trigger = document.getElementById('tab-btn-api-v4');
  if (!menu || !trigger) return;

  if (!menu.hidden && !menu.contains(event.target) && !trigger.contains(event.target)) {
      closeApiV4Menu();
  }
});

// Initialize Server-Sent Events connection for real-time updates
function initializeEventStream() {
  if (eventSource) {
      eventSource.close();
  }

  console.log('Connecting to event stream at /api/events...');
  eventSource = new EventSource('/api/events');

  eventSource.onopen = function(event) {
      console.log('Event stream connected');
  };

  eventSource.onmessage = function(event) {
      console.log('šŸ“„ Received SSE event (raw):', event);
      console.log('šŸ“„ Event data:', event.data);
      console.log('šŸ“„ Event id:', event.lastEventId);
      console.log('šŸ“„ Event type:', event.type);

      // Ignore empty or whitespace-only messages (keep-alive)
      if (!event.data || event.data.trim() === '') {
          console.log('ā­ļø Ignoring empty keep-alive message');
          return;
      }

      try {
          // Parse once - SSE may double-encode
          let data = JSON.parse(event.data);

          // If it's a string, parse again (double-encoded)
          if (typeof data === 'string') {
              console.log('šŸ“„ Detected double-encoded data, parsing again...');
              data = JSON.parse(data);
          }

          console.log('šŸ“„ Parsed event data:', data);

          // Ignore only connection acknowledgment messages
          if (data.type === 'connected') {
              console.log('āœ… SSE stream established and ready');
              return;
          }

          // Process all other messages with a type
          if (!data.type) {
              console.log('ā­ļø Ignoring message without type:', data);
              return;
          }

          console.log(`✨ Enrollment ${data.type}: ${data.enrollmentId || 'unknown'} - Auto-refreshing...`);

          // Show a brief update indicator
          showUpdateNotification(data.type);

          // Auto-refresh the enrollment list when an event occurs
          setTimeout(() => {
              console.log('šŸ”„ Triggering enrollment list refresh...');
              loadEnrollments();
          }, 500); // Small delay to ensure server has processed the change
      } catch (err) {
          console.error('Error parsing event data:', err, 'Raw data:', event.data);
          // Don't show notifications for parse errors (likely keep-alive messages)
      }
  };

  eventSource.onerror = function(event) {
      console.error('āš ļø Event stream error:', event);
      console.error('EventSource readyState:', eventSource.readyState,
          eventSource.readyState === EventSource.CONNECTING ? '(CONNECTING)' :
          eventSource.readyState === EventSource.OPEN ? '(OPEN)' :
          eventSource.readyState === EventSource.CLOSED ? '(CLOSED)' : '(UNKNOWN)');

      if (eventSource.readyState === EventSource.CLOSED) {
          console.log('Event stream closed, attempting to reconnect in 5 seconds...');
          setTimeout(initializeEventStream, 5000);
      }
  };
}

async function loadEnrollments() {
  const loading = document.getElementById('loading');
  const error = document.getElementById('error');
  const container = document.getElementById('enrollments-container');
  const matchCount = document.getElementById('match-count');

  loading.style.display = 'block';
  error.style.display = 'none';
  container.innerHTML = '';
  if (matchCount) {
      matchCount.textContent = 'Loading enrollments...';
  }

  try {
      // Add timestamp to prevent caching
      const timestamp = new Date().getTime();
      console.log('Fetching enrollments from /api/enrollments with cache buster:', timestamp);
      const response = await fetch(`/api/enrollments?_=${timestamp}`, {
          cache: 'no-store',
          headers: {
              'Cache-Control': 'no-cache, no-store, must-revalidate',
              'Pragma': 'no-cache'
          }
      });
      console.log('Response status:', response.status);

      const data = await response.json();
      console.log('Received data:', data);

      if (data.error) {
          throw new Error(data.error);
      }

      document.getElementById('atsign-display').textContent = data.atSign;
      allEnrollments = data.enrollments || [];

      // Sort enrollments: pending first, then approved, revoked, denied
      const statusPriority = { 'pending': 1, 'approved': 2, 'revoked': 3, 'denied': 4 };
      allEnrollments.sort((a, b) => {
          const priorityA = statusPriority[a.status] || 5;
          const priorityB = statusPriority[b.status] || 5;
          return priorityA - priorityB;
      });

      console.log('Loaded', allEnrollments.length, 'enrollments');

      loading.style.display = 'none';
      if (matchCount) {
          matchCount.textContent = `Showing ${allEnrollments.length} enrollments`;
      }

      if (allEnrollments.length === 0) {
          container.innerHTML = '<p style="grid-column: 1/-1; text-align: center; color: #666; padding: 40px;">No enrollments found for this atSign.</p>';
      } else {
          filterEnrollments();
      }

  } catch (err) {
      console.error('Error loading enrollments:', err);
      loading.style.display = 'none';
      error.textContent = 'Error loading enrollments: ' + err.message;
      error.style.display = 'block';
      if (matchCount) {
          matchCount.textContent = 'Unable to load enrollments';
      }
  }
}

function filterEnrollments() {
  console.log('=== FILTERING ENROLLMENTS ===');
  console.log('Total enrollments to filter:', allEnrollments.length);
  allEnrollments.forEach(e => {
      console.log(`  - ${e.enrollmentId}: status=${e.status}`);
  });

  const showAll = document.getElementById('show-all').checked;
  const approved = document.getElementById('show-approved').checked;
  const pending = document.getElementById('show-pending').checked;
  const revoked = document.getElementById('show-revoked').checked;
  const denied = document.getElementById('show-denied').checked;
  const searchTerm = document.getElementById('search-input').value.toLowerCase();
  const container = document.getElementById('enrollments-container');
  const matchCount = document.getElementById('match-count');
  const clearButton = document.getElementById('clear-search');

  console.log('šŸ” Current filter settings:');
  console.log(`  Show All: ${showAll}`);
  console.log(`  Approved: ${approved}`);
  console.log(`  Pending: ${pending}`);
  console.log(`  Revoked: ${revoked}`);
  console.log(`  Denied: ${denied}`);
  console.log(`  Search term: "${searchTerm}"`);

  container.innerHTML = '';

  // Show/hide clear button based on search input
  if (searchTerm) {
      clearButton.style.display = 'flex';
  } else {
      clearButton.style.display = 'none';
  }

  let filteredEnrollments = allEnrollments;

  // Filter by status
  if (!showAll) {
      filteredEnrollments = filteredEnrollments.filter(enrollment => {
          if (enrollment.status === 'approved' && approved) return true;
          if (enrollment.status === 'pending' && pending) return true;
          if (enrollment.status === 'revoked' && revoked) return true;
          if (enrollment.status === 'denied' && denied) return true;
          return false;
      });
      console.log(`After status filter: ${filteredEnrollments.length} enrollments`);
  }

  // Filter by search term
  if (searchTerm) {
      filteredEnrollments = filteredEnrollments.filter(enrollment => {
          const enrollmentId = (enrollment.enrollmentId || '').toLowerCase();
          const appName = (enrollment.appName || '').toLowerCase();
          const deviceName = (enrollment.deviceName || '').toLowerCase();
          const namespace = (enrollment.namespace || '').toLowerCase();

          return enrollmentId.includes(searchTerm) ||
                 appName.includes(searchTerm) ||
                 deviceName.includes(searchTerm) ||
                 namespace.includes(searchTerm);
      });
  }

  // Update match count
  const totalCount = allEnrollments.length;
  const matchedCount = filteredEnrollments.length;

  if (searchTerm) {
      matchCount.textContent = `Showing ${matchedCount} of ${totalCount} enrollments`;
  } else if (!showAll) {
      matchCount.textContent = `Showing ${matchedCount} of ${totalCount} enrollments`;
  } else {
      matchCount.textContent = `Showing ${totalCount} enrollments`;
  }

  if (filteredEnrollments.length === 0) {
      container.innerHTML = '<p style="grid-column: 1/-1; text-align: center; color: #666; padding: 40px;">No enrollments matching current filters</p>';
      console.log('No enrollments after filtering');
      return;
  }

  console.log(`Creating ${filteredEnrollments.length} enrollment cards`);
  filteredEnrollments.forEach((enrollment) => {
      console.log(`  Creating card for ${enrollment.enrollmentId} (${enrollment.status})`);
      const card = createEnrollmentCard(enrollment);
      container.appendChild(card);
  });
  console.log(`Finished rendering ${filteredEnrollments.length} cards to DOM`);
}

function clearSearch() {
  document.getElementById('search-input').value = '';
  filterEnrollments();
}

let currentGeneratedOtp = '';
let currentEnrollmentCommand = '';
const generatedOtpHistory = [];
const autoApprovalServices = [];
const openAutoApprovalLogIds = new Set();
const keyHistoryEntries = [];
let visibleHistoryCount = 5;
let otpCountdownTimer = null;

function formatOtpTimestamp(value) {
  try {
      return new Date(value).toLocaleString();
  } catch (_) {
      return value;
  }
}

function normalizeOtpExpiryLabel(value) {
  const trimmed = (value || '').trim();
  return trimmed || 'default';
}

function parseOtpExpiryMs(value) {
  const trimmed = (value || '').trim();
  if (!trimmed) return null;

  const matches = Array.from(trimmed.matchAll(/(\d+)(ms|d|h|m|s)/g));
  if (matches.length === 0) return null;

  const combined = matches.map((match) => match[0]).join('');
  if (combined !== trimmed.replace(/\s+/g, '')) {
      return null;
  }

  let totalMs = 0;
  matches.forEach((match) => {
      const amount = Number(match[1]);
      const unit = match[2];
      if (unit === 'd') totalMs += amount * 24 * 60 * 60 * 1000;
      if (unit === 'h') totalMs += amount * 60 * 60 * 1000;
      if (unit === 'm') totalMs += amount * 60 * 1000;
      if (unit === 's') totalMs += amount * 1000;
      if (unit === 'ms') totalMs += amount;
  });

  return totalMs > 0 ? totalMs : null;
}

function computeOtpExpiresAtUtc(expiry) {
  const durationMs = parseOtpExpiryMs(expiry);
  if (!durationMs) return '';
  return new Date(Date.now() + durationMs).toISOString();
}

function isOtpEntrySelectable(entry) {
  if (!entry || !entry.otp) return false;
  if (!entry.expiresAtUtc) return false;
  const expiresAtMs = Date.parse(entry.expiresAtUtc);
  if (Number.isNaN(expiresAtMs)) return false;
  return expiresAtMs > Date.now();
}

function getSelectableOtpEntries() {
  return generatedOtpHistory.filter(isOtpEntrySelectable);
}

function renderEnrollmentOtpOptions() {
  const select = document.getElementById('enrollment-otp-select');
  if (!select) return;

  const previousValue = select.value;
  const validEntries = getSelectableOtpEntries();
  const options = [
      '<option value="__new__">Create New OTP 1h</option>',
      ...validEntries.map((entry, index) => {
          const generatedAt = formatOtpTimestamp(entry.generatedAtUtc);
          const timeLeft = formatTimeLeft(entry.expiresAtUtc);
          return `<option value="${escapeHtml(String(index))}">${escapeHtml(`${entry.otp} • ${generatedAt} • ${timeLeft} left`)}</option>`;
      })
  ];

  select.innerHTML = options.join('');
  if (previousValue && Array.from(select.options).some((option) => option.value === previousValue)) {
      select.value = previousValue;
  } else {
      select.value = '__new__';
  }
}

function formatHistoryData(data) {
  if (!data || typeof data !== 'object') {
      return '';
  }
  const entries = Object.entries(data);
  if (entries.length === 0) {
      return '';
  }
  return entries.map(([key, value]) => `${key}: ${typeof value === 'string' ? value : JSON.stringify(value)}`).join(' | ');
}

function renderKeyHistory() {
  const empty = document.getElementById('history-empty');
  const list = document.getElementById('history-list');
  const loadMoreButton = document.getElementById('history-load-more-btn');
  if (!empty || !list || !loadMoreButton) return;

  if (keyHistoryEntries.length === 0) {
      empty.hidden = false;
      list.hidden = true;
      list.innerHTML = '';
      loadMoreButton.hidden = true;
      return;
  }

  empty.hidden = true;
  list.hidden = false;
  const visibleEntries = keyHistoryEntries.slice(0, visibleHistoryCount);
  list.innerHTML = visibleEntries.map((entry) => `
      <div class="history-item">
          <div class="history-item-header">
              <div class="history-item-type">${escapeHtml((entry.type || 'event').replaceAll('_', ' '))}</div>
              <div class="history-item-time">${escapeHtml(formatOtpTimestamp(entry.createdAtUtc))}</div>
          </div>
          <div class="history-item-summary">${escapeHtml(entry.summary || '')}</div>
          ${formatHistoryData(entry.data) ? `<div class="history-item-data">${escapeHtml(formatHistoryData(entry.data))}</div>` : ''}
      </div>
  `).join('');
  loadMoreButton.hidden = visibleHistoryCount >= keyHistoryEntries.length;
}

function loadMoreHistory() {
  visibleHistoryCount += 10;
  renderKeyHistory();
}

function formatTimeLeft(expiresAtUtc) {
  if (!expiresAtUtc) {
      return 'Default server expiry';
  }

  const expiresAtMs = Date.parse(expiresAtUtc);
  if (Number.isNaN(expiresAtMs)) {
      return 'Unknown';
  }

  const diffMs = expiresAtMs - Date.now();
  if (diffMs <= 0) {
      return 'Expired';
  }

  let remainingMs = diffMs;
  const parts = [];
  const units = [
      { label: 'd', ms: 24 * 60 * 60 * 1000 },
      { label: 'h', ms: 60 * 60 * 1000 },
      { label: 'm', ms: 60 * 1000 },
      { label: 's', ms: 1000 }
  ];

  units.forEach((unit) => {
      if (remainingMs >= unit.ms || (unit.label === 's' && parts.length === 0)) {
          const value = Math.floor(remainingMs / unit.ms);
          if (value > 0 || unit.label === 's') {
              parts.push(`${value}${unit.label}`);
              remainingMs -= value * unit.ms;
          }
      }
  });

  return parts.join(' ');
}

function syncOtpCountdownTimer() {
  const hasCountdowns = generatedOtpHistory.some((entry) => entry.expiresAtUtc);
  if (hasCountdowns && !otpCountdownTimer) {
      otpCountdownTimer = window.setInterval(renderOtpHistory, 1000);
  }
  if (!hasCountdowns && otpCountdownTimer) {
      window.clearInterval(otpCountdownTimer);
      otpCountdownTimer = null;
  }
}

function renderOtpHistory() {
  const empty = document.getElementById('otp-history-empty');
  const wrap = document.getElementById('otp-history-table-wrap');
  const body = document.getElementById('otp-history-body');
  if (!empty || !wrap || !body) return;

  if (generatedOtpHistory.length === 0) {
      empty.hidden = false;
      wrap.hidden = true;
      body.innerHTML = '';
      syncOtpCountdownTimer();
      renderEnrollmentOtpOptions();
      return;
  }

  empty.hidden = true;
  wrap.hidden = false;
  body.innerHTML = generatedOtpHistory.map((entry, index) => `
      <tr>
          <td>${escapeHtml(formatOtpTimestamp(entry.generatedAtUtc))}</td>
          <td>${escapeHtml(normalizeOtpExpiryLabel(entry.expiry))}</td>
          <td>
              <div>${escapeHtml(entry.expiresAtUtc ? formatOtpTimestamp(entry.expiresAtUtc) : 'Default server expiry')}</div>
              <div class="otp-time-left">${escapeHtml(formatTimeLeft(entry.expiresAtUtc))}</div>
          </td>
          <td><button type="button" class="otp-history-copy-btn" data-otp-index="${index}">${escapeHtml(entry.otp)}</button></td>
          <td><button type="button" class="otp-history-clear-btn" data-otp-clear-index="${index}">Clear</button></td>
      </tr>
  `).join('');
  syncOtpCountdownTimer();
  renderEnrollmentOtpOptions();

  body.querySelectorAll('[data-otp-index]').forEach((button) => {
      button.addEventListener('click', async function () {
          const index = Number(button.getAttribute('data-otp-index'));
          const entry = generatedOtpHistory[index];
          if (!entry) return;

          currentGeneratedOtp = entry.otp;
          await navigator.clipboard.writeText(entry.otp);
      });
  });

  body.querySelectorAll('[data-otp-clear-index]').forEach((button) => {
      button.addEventListener('click', async function () {
          const index = Number(button.getAttribute('data-otp-clear-index'));
          const entry = generatedOtpHistory[index];
          if (!entry) return;
          await clearOtpHistoryEntry(entry);
      });
  });
}

async function persistOtpHistory(otp, expiry, expiresAtUtc) {
  const response = await fetch('/api/key-state/otp', {
      method: 'POST',
      headers: {
          'Content-Type': 'application/json'
      },
      body: JSON.stringify({
          otp,
          expiry: normalizeOtpExpiryLabel(expiry),
          expiresAtUtc: expiresAtUtc || ''
      })
  });
  const data = await response.json();
  if (!response.ok || data.error) {
      throw new Error(data.error || 'Failed to save OTP history');
  }

  generatedOtpHistory.length = 0;
  const history = Array.isArray(data.otpHistory) ? data.otpHistory : [];
  history.forEach((entry) => generatedOtpHistory.push(entry));
  renderOtpHistory();
}

async function loadPersistedOtpHistory() {
  try {
      const response = await fetch('/api/key-state');
      const data = await response.json();
      if (!response.ok || data.error) {
          throw new Error(data.error || 'Failed to load OTP history');
      }

      generatedOtpHistory.length = 0;
      const history = Array.isArray(data.otpHistory) ? data.otpHistory : [];
      history.forEach((entry) => generatedOtpHistory.push(entry));
      keyHistoryEntries.length = 0;
      const keyHistory = Array.isArray(data.history) ? data.history : [];
      keyHistory.forEach((entry) => keyHistoryEntries.push(entry));
      visibleHistoryCount = 5;
      renderOtpHistory();
      renderKeyHistory();

      if (history.length > 0) {
          const latest = history[0];
          currentGeneratedOtp = latest.otp || '';
      }
  } catch (_) {
      renderOtpHistory();
      renderKeyHistory();
  }
}

async function clearOtpHistoryEntry(entry) {
  const response = await fetch('/api/key-state/otp/delete', {
      method: 'POST',
      headers: {
          'Content-Type': 'application/json'
      },
      body: JSON.stringify({
          otp: entry.otp || '',
          generatedAtUtc: entry.generatedAtUtc || ''
      })
  });
  const data = await response.json();
  if (!response.ok || data.error) {
      throw new Error(data.error || 'Failed to clear OTP history');
  }

  generatedOtpHistory.length = 0;
  const history = Array.isArray(data.otpHistory) ? data.otpHistory : [];
  history.forEach((item) => generatedOtpHistory.push(item));
  currentGeneratedOtp = history.length > 0 ? (history[0].otp || '') : '';
  keyHistoryEntries.length = 0;
  const keyHistory = Array.isArray(data.history) ? data.history : [];
  keyHistory.forEach((entry) => keyHistoryEntries.push(entry));
  renderOtpHistory();
  renderKeyHistory();
}

function formatAutoApprovalStatus(value) {
  const status = (value || 'stopped').toString();
  return status.charAt(0).toUpperCase() + status.slice(1);
}

function formatAutoApprovalLogLines(service) {
  const command = (service.command || '').toString().trim();
  const logs = Array.isArray(service.logs) ? service.logs : [];
  const lines = [];

  if (command) {
      lines.push(`$ ${command}`);
      lines.push('');
  }

  if (logs.length === 0) {
      lines.push('No process logs yet.');
  } else {
      logs.forEach((entry) => {
          const timestamp = entry.timestampUtc ? formatOtpTimestamp(entry.timestampUtc) : 'Unknown time';
          const stream = (entry.stream || 'stdout').toString().toUpperCase();
          const line = (entry.line || '').toString();
          lines.push(`[${timestamp}] ${stream} ${line}`);
      });
  }

  return lines.join('\n');
}

function renderAutoApprovalServices() {
  const empty = document.getElementById('auto-approval-empty');
  const list = document.getElementById('auto-approval-list');
  if (!empty || !list) return;

  if (autoApprovalServices.length === 0) {
      empty.hidden = false;
      list.hidden = true;
      list.innerHTML = '';
      return;
  }

  empty.hidden = true;
  list.hidden = false;
  list.innerHTML = autoApprovalServices.map((service) => {
      const status = (service.status || 'stopped').toString();
      const stopDisabled = status === 'completed' || status === 'stopped' || status === 'error';
      const startDisabled = status === 'running' || status === 'starting';
      const createdAt = service.createdAtUtc ? formatOtpTimestamp(service.createdAtUtc) : 'Unknown';
      const startedAt = service.startedAtUtc ? formatOtpTimestamp(service.startedAtUtc) : 'Not started';
      const approveExisting = service.approveExisting ? 'Yes' : 'No';
      const lastError = (service.lastError || '').toString().trim();
      const lastOutput = (service.lastOutput || '').toString().trim();
      const approvedIds = Array.isArray(service.approvedEnrollmentIds) ? service.approvedEnrollmentIds : [];
      const logsText = formatAutoApprovalLogLines(service);
      const approvedListHtml = approvedIds.length
          ? `<div class="auto-approval-approved-list">${approvedIds.map((id) => `<code>${escapeHtml(String(id))}</code>`).join('')}</div>`
          : '<span>None</span>';
      return `
          <div class="auto-approval-card">
              <div class="auto-approval-card-header">
                  <div class="auto-approval-card-title">${escapeHtml(service.id || 'Auto Approval Service')}</div>
                  <div class="auto-approval-card-status ${escapeHtml(status)}">${escapeHtml(formatAutoApprovalStatus(status))}</div>
              </div>
              <div class="auto-approval-card-grid">
                  <div class="auto-approval-card-row">
                      <strong>App Name Regex</strong>
                      <code>${escapeHtml(service.appNameRegex || '')}</code>
                  </div>
                  <div class="auto-approval-card-row">
                      <strong>Device Name Regex</strong>
                      <code>${escapeHtml(service.deviceNameRegex || '')}</code>
                  </div>
                  <div class="auto-approval-card-row">
                      <strong>Limit</strong>
                      <span>${escapeHtml(String(service.limit || 1))}</span>
                  </div>
                  <div class="auto-approval-card-row">
                      <strong>Approve Existing</strong>
                      <span>${escapeHtml(approveExisting)}</span>
                  </div>
                  <div class="auto-approval-card-row">
                      <strong>Created</strong>
                      <span>${escapeHtml(createdAt)}</span>
                  </div>
                  <div class="auto-approval-card-row">
                      <strong>Last Started</strong>
                      <span>${escapeHtml(startedAt)}</span>
                  </div>
                  <div class="auto-approval-card-row">
                      <strong>Last Output</strong>
                      <span>${escapeHtml(lastOutput || 'None')}</span>
                  </div>
                  <div class="auto-approval-card-row">
                      <strong>Last Error</strong>
                      <span>${escapeHtml(lastError || 'None')}</span>
                  </div>
                  <div class="auto-approval-card-row auto-approval-card-row-wide">
                      <strong>Approved Enrollment UUIDs (${escapeHtml(String(approvedIds.length))})</strong>
                      ${approvedListHtml}
                  </div>
              </div>
              <div class="auto-approval-card-actions">
                  <button type="button" class="api-v4-secondary" onclick="startAutoApprovalService('${escapeJsString(service.id || '')}')" ${startDisabled ? 'disabled' : ''}>Start</button>
                  <button type="button" class="api-v4-secondary" onclick="stopAutoApprovalService('${escapeJsString(service.id || '')}')" ${stopDisabled ? 'disabled' : ''}>Stop</button>
                  <button type="button" class="api-v4-secondary" onclick="toggleAutoApprovalLogs('${escapeJsString(service.id || '')}')">${openAutoApprovalLogIds.has(String(service.id || '')) ? 'Hide Logs' : 'View Logs'}</button>
                  <button type="button" class="api-v4-secondary auto-approval-delete-btn" onclick="deleteAutoApprovalService('${escapeJsString(service.id || '')}')">Delete</button>
              </div>
              <div class="auto-approval-log-panel" id="auto-approval-logs-${escapeHtml(service.id || '')}" ${openAutoApprovalLogIds.has(String(service.id || '')) ? '' : 'hidden'}>
                  <div class="auto-approval-log-label">Executed Command and Process Logs</div>
                  <pre class="auto-approval-log-output">${escapeHtml(logsText)}</pre>
              </div>
          </div>
      `;
  }).join('');
}

function toggleAutoApprovalLogs(serviceId) {
  const panel = document.getElementById(`auto-approval-logs-${serviceId}`);
  if (!panel) return;
  panel.hidden = !panel.hidden;
  if (panel.hidden) {
      openAutoApprovalLogIds.delete(serviceId);
  } else {
      openAutoApprovalLogIds.add(serviceId);
  }
  renderAutoApprovalServices();
}

function applyAutoApprovalServicesFromPayload(data) {
  autoApprovalServices.length = 0;
  const services = Array.isArray(data.autoApprovalServices) ? data.autoApprovalServices : [];
  services.forEach((service) => autoApprovalServices.push(service));
  if (Array.isArray(data.history)) {
      keyHistoryEntries.length = 0;
      data.history.forEach((entry) => keyHistoryEntries.push(entry));
      renderKeyHistory();
  }
  const validIds = new Set(services.map((service) => String(service.id || '')));
  Array.from(openAutoApprovalLogIds).forEach((serviceId) => {
      if (!validIds.has(serviceId)) {
          openAutoApprovalLogIds.delete(serviceId);
      }
  });
  renderAutoApprovalServices();
}

async function loadAutoApprovalServices() {
  try {
      const response = await fetch('/api/auto-approval-services');
      const data = await response.json();
      if (!response.ok || data.error) {
          throw new Error(data.error || 'Failed to load auto approval services');
      }
      applyAutoApprovalServicesFromPayload(data);
  } catch (error) {
      console.error('Error loading auto approval services:', error);
      renderAutoApprovalServices();
  }
}

async function createAutoApprovalService() {
  try {
      const appNameRegex = (document.getElementById('auto-approval-app-regex')?.value || '').trim();
      const deviceNameRegex = (document.getElementById('auto-approval-device-regex')?.value || '').trim();
      const limit = (document.getElementById('auto-approval-limit')?.value || '1').trim() || '1';
      const approveExisting = document.getElementById('auto-approval-approve-existing')?.checked === true;

      if (!appNameRegex || !deviceNameRegex) {
          throw new Error('App Name Regex and Device Name Regex are required');
      }

      const response = await fetch('/api/auto-approval-services', {
          method: 'POST',
          headers: {
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              appNameRegex,
              deviceNameRegex,
              limit,
              approveExisting
          })
      });
      const data = await response.json();
      if (!response.ok || data.error) {
          throw new Error(data.error || 'Failed to create auto approval service');
      }

      applyAutoApprovalServicesFromPayload(data);
  } catch (error) {
      alert(error.message || 'Failed to create auto approval service');
  }
}

async function startAutoApprovalService(serviceId) {
  try {
      const response = await fetch(`/api/auto-approval-services/${encodeURIComponent(serviceId)}/start`, {
          method: 'POST'
      });
      const data = await response.json();
      if (!response.ok || data.error) {
          throw new Error(data.error || 'Failed to start auto approval service');
      }
      applyAutoApprovalServicesFromPayload(data);
  } catch (error) {
      alert(error.message || 'Failed to start auto approval service');
  }
}

async function stopAutoApprovalService(serviceId) {
  try {
      const response = await fetch(`/api/auto-approval-services/${encodeURIComponent(serviceId)}/stop`, {
          method: 'POST'
      });
      const data = await response.json();
      if (!response.ok || data.error) {
          throw new Error(data.error || 'Failed to stop auto approval service');
      }
      applyAutoApprovalServicesFromPayload(data);
  } catch (error) {
      alert(error.message || 'Failed to stop auto approval service');
  }
}

async function deleteAutoApprovalService(serviceId) {
  if (!confirm('Delete this auto approval service?')) {
      return;
  }

  try {
      const response = await fetch(`/api/auto-approval-services/${encodeURIComponent(serviceId)}`, {
          method: 'DELETE'
      });
      const data = await response.json();
      if (!response.ok || data.error) {
          throw new Error(data.error || 'Failed to delete auto approval service');
      }
      applyAutoApprovalServicesFromPayload(data);
  } catch (error) {
      alert(error.message || 'Failed to delete auto approval service');
  }
}

async function requestOtp(expiryOverride = null) {
  const expiry = expiryOverride == null ? getOtpExpiryValue() : String(expiryOverride).trim();

  try {
      const otpUrl = expiry ? `/api/otp?expiry=${encodeURIComponent(expiry)}` : '/api/otp';
      const response = await fetch(otpUrl);
      const data = await response.json();
      if (!response.ok || data.error) {
          throw new Error(data.error || 'Failed to generate OTP');
      }

      currentGeneratedOtp = data.otp || '';
      await persistOtpHistory(
          data.otp || '',
          data.expiry || expiry,
          computeOtpExpiresAtUtc(data.expiry || expiry)
      );
      return data.otp || '';
  } catch (err) {
      throw err;
  }
}

async function generateOtp() {
  return requestOtp();
}

async function generateOtpWithExpiry(expiry) {
  return requestOtp(expiry || '1h');
}

function getOtpExpiryValue() {
  const input = document.getElementById('otp-expiry-input');
  return input ? input.value.trim() : '';
}

function renderNamespacePreview() {
  const preview = document.getElementById('namespace-preview');
  if (!preview) return;

  const values = getNamespacePermissionValues();
  preview.textContent = values.length ? values.map((item) => `${item.namespace}:${item.permission}`).join(',') : 'No namespaces configured yet.';
}

function addNamespacePermissionRow(namespace = '', permission = 'rw') {
  const list = document.getElementById('namespace-permission-list');
  if (!list) return;

  const row = document.createElement('div');
  row.className = 'namespace-permission-row';
  row.innerHTML = `
      <input type="text" class="namespace-input" placeholder="namespace" value="${escapeHtml(namespace)}" />
      <select class="permission-input">
          <option value="r" ${permission === 'r' ? 'selected' : ''}>r</option>
          <option value="rw" ${permission === 'rw' ? 'selected' : ''}>rw</option>
          <option value="w" ${permission === 'w' ? 'selected' : ''}>w</option>
          <option value="*" ${permission === '*' ? 'selected' : ''}>*</option>
      </select>
      <button type="button" class="api-v4-secondary namespace-remove-btn">Remove</button>
  `;

  row.querySelector('.namespace-input').addEventListener('input', renderNamespacePreview);
  row.querySelector('.permission-input').addEventListener('change', renderNamespacePreview);
  row.querySelector('.namespace-remove-btn').addEventListener('click', function () {
      row.remove();
      renderNamespacePreview();
  });

  list.appendChild(row);
  renderNamespacePreview();
}

function resetNamespacePermissionRows(rows) {
  const list = document.getElementById('namespace-permission-list');
  if (!list) return;

  list.innerHTML = '';
  rows.forEach((row) => addNamespacePermissionRow(row.namespace, row.permission));
  renderNamespacePreview();
}

function getNamespacePermissionValues() {
  const list = document.getElementById('namespace-permission-list');
  if (!list) return [];

  return Array.from(list.querySelectorAll('.namespace-permission-row'))
      .map((row) => ({
          namespace: (row.querySelector('.namespace-input')?.value || '').trim(),
          permission: (row.querySelector('.permission-input')?.value || '').trim()
      }))
      .filter((item) => item.namespace);
}

function setEnrollmentCommandResult({ command = '', error = '' } = {}) {
  const result = document.getElementById('enrollment-command-result');
  const value = document.getElementById('enrollment-command-value');
  const meta = document.getElementById('enrollment-command-meta');
  const copyButton = document.getElementById('copy-enrollment-command-btn');
  if (!result || !value || !meta || !copyButton) return;

  if (error) {
      currentEnrollmentCommand = '';
      value.textContent = error;
      meta.textContent = '';
      result.hidden = false;
      result.classList.add('otp-inline-result-error');
      copyButton.hidden = true;
      return;
  }

  if (!command) {
      currentEnrollmentCommand = '';
      value.textContent = '';
      meta.textContent = '';
      result.hidden = true;
      result.classList.remove('otp-inline-result-error');
      copyButton.hidden = true;
      return;
  }

  currentEnrollmentCommand = command;
  value.textContent = command;
  meta.textContent = 'Generated from the current form values';
  result.hidden = false;
  result.classList.remove('otp-inline-result-error');
  copyButton.hidden = false;
}

function applyEnrollmentProfile(profile) {
  const deviceNameInput = document.getElementById('enroll-device-name');
  const appNameInput = document.getElementById('enroll-app-name');
  if (!deviceNameInput || !appNameInput) return;

  if (profile === 'blank') {
      deviceNameInput.value = '';
      appNameInput.value = '';
      resetNamespacePermissionRows([]);
  }

  if (profile === 'noports') {
      deviceNameInput.value = '';
      appNameInput.value = 'noports';
      resetNamespacePermissionRows([
          { namespace: 'sshnp', permission: 'rw' },
          { namespace: 'sshrvd', permission: 'rw' }
      ]);
  }

  if (profile === 'personalagent') {
      deviceNameInput.value = '';
      appNameInput.value = 'personalagent';
      resetNamespacePermissionRows([
          { namespace: 'personalagent', permission: 'rw' }
      ]);
  }

  setEnrollmentCommandResult();
}

function showNoPortsWizard() {
  applyEnrollmentProfile('noports');
}

function showPersonalAgentWizard() {
  applyEnrollmentProfile('personalagent');
}

function validateDeviceName(deviceName) {
  if (!deviceName) {
      throw new Error('Please enter a deviceName');
  }

  if (!/^[a-zA-Z0-9_-]+$/.test(deviceName)) {
      throw new Error('deviceName can only contain letters, numbers, hyphens, and underscores');
  }

  if (allEnrollments && allEnrollments.length > 0) {
      const existingDevice = allEnrollments.find(e =>
          e.deviceName === deviceName &&
          e.status === 'approved' &&
          !e.isRevoked
      );

      if (existingDevice) {
          throw new Error(`deviceName "${deviceName}" is already in use by an active enrollment`);
      }
  }
}

function buildKeyPath(atSign) {
  return `~/.atsign/keys/${atSign}_key.atKeys`;
}

async function generateEnrollmentCommand() {
  try {
      const atSignElement = document.getElementById('atsign-display');
      const atSign = atSignElement.textContent.trim();
      const deviceName = (document.getElementById('enroll-device-name')?.value || '').trim();
      const appName = (document.getElementById('enroll-app-name')?.value || '').trim();
      const otpSelect = document.getElementById('enrollment-otp-select');
      const namespaceValues = getNamespacePermissionValues();

      validateDeviceName(deviceName);
      if (!appName) {
          throw new Error('Please enter an appName');
      }
      if (namespaceValues.length === 0) {
          throw new Error('Add at least one namespace + permission entry');
      }

      let otp = '';
      if (otpSelect && otpSelect.value !== '__new__') {
          const selectedEntry = getSelectableOtpEntries()[Number(otpSelect.value)];
          if (!selectedEntry) {
              throw new Error('Selected OTP is no longer valid');
          }
          otp = selectedEntry.otp;
      } else {
          otp = await generateOtpWithExpiry('1h');
          renderEnrollmentOtpOptions();
      }
      const namespaces = namespaceValues.map((item) => `${item.namespace}:${item.permission}`).join(',');
      const command = `~/.local/bin/at_activate enroll -a ${atSign} \\
-s ${otp} \\
-p ${appName} \\
-k ${buildKeyPath(atSign)} \\
-d ${deviceName} \\
-n "${namespaces}"`;

      setEnrollmentCommandResult({ command });
  } catch (err) {
      setEnrollmentCommandResult({ error: `Failed to generate command: ${err.message}` });
  }
}

async function copyEnrollmentCommand() {
  if (!currentEnrollmentCommand) return;

  try {
      await navigator.clipboard.writeText(currentEnrollmentCommand);
      const meta = document.getElementById('enrollment-command-meta');
      if (meta) {
          meta.textContent = 'Copied to clipboard';
      }
  } catch (err) {
      alert('Could not copy command to clipboard. Please copy it manually.');
  }
}

function createEnrollmentCard(enrollment) {
  const card = document.createElement('div');
  card.className = `enrollment-card ${enrollment.status}`;
  const cardId = 'card-' + Math.random().toString(36).substr(2, 9);

  const statusText = enrollment.status.charAt(0).toUpperCase() + enrollment.status.slice(1);
  const keyCount = Object.keys(enrollment.apkamKeys || {}).length;

  card.innerHTML = `
      <div class="status-badge ${enrollment.status}">${statusText}</div>
      <div class="enrollment-id">${escapeHtml(enrollment.enrollmentId)}</div>
      <div class="enrollment-details">
          <div class="detail-row">
              <span class="detail-label">App Name:</span>
              <span class="detail-value">${escapeHtml(enrollment.appName)}</span>
          </div>
          <div class="detail-row">
              <span class="detail-label">Device:</span>
              <span class="detail-value">${escapeHtml(enrollment.deviceName)}</span>
          </div>
          <div class="detail-row">
              <span class="detail-label">Namespace:</span>
              <span class="detail-value">${escapeHtml(enrollment.namespace)}</span>
          </div>
          <div class="detail-row">
              <span class="detail-label">APKAM Keys:</span>
              <span class="detail-value">${keyCount}</span>
          </div>
      </div>
      <div class="enrollment-actions">
          <button class="btn btn-view" onclick="toggleKeys('${cardId}')">View Keys</button>
          ${enrollment.status === 'pending'
              ? '<button class="btn btn-approve" onclick="approveEnrollment(\'' + escapeHtml(enrollment.enrollmentId) + '\')">Approve</button>' +
                '<button class="btn btn-revoke" onclick="denyEnrollment(\'' + escapeHtml(enrollment.enrollmentId) + '\')">Deny</button>'
              : ''
          }
          ${enrollment.status === 'approved'
              ? '<button class="btn btn-revoke" onclick="revokeEnrollment(\'' + escapeHtml(enrollment.enrollmentId) + '\')">Revoke</button>'
              : ''
          }
          ${enrollment.status === 'revoked'
              ? '<button class="btn btn-approve" onclick="unrevokeEnrollment(\'' + escapeHtml(enrollment.enrollmentId) + '\')">Unrevoke</button>' +
                '<button class="btn btn-delete" onclick="deleteEnrollment(\'' + escapeHtml(enrollment.enrollmentId) + '\')">Delete</button>'
              : ''
          }
          ${enrollment.status === 'denied'
              ? '<button class="btn btn-delete" onclick="deleteEnrollment(\'' + escapeHtml(enrollment.enrollmentId) + '\')">Delete</button>'
              : ''
          }
      </div>
      <div id="${cardId}" class="keys-section">
          <div class="keys-list">
              ${generateKeysHtml(enrollment.apkamKeys)}
          </div>
      </div>
  `;

  return card;
}

function generateKeysHtml(apkamKeys) {
  if (!apkamKeys || Object.keys(apkamKeys).length === 0) {
      return '<p style="color: #666; text-align: center; padding: 20px;">No keys found</p>';
  }

  let html = '';
  for (const [keyName, keyValue] of Object.entries(apkamKeys)) {
      html += `
          <div class="key-item">
              <div class="key-name">${escapeHtml(keyName)}</div>
              <div class="key-value">${escapeHtml(keyValue || '(empty)')}</div>
          </div>
      `;
  }
  return html;
}

function toggleKeys(cardId) {
  const keysSection = document.getElementById(cardId);
  if (keysSection) {
      keysSection.classList.toggle('show');
  }
}

async function approveEnrollment(enrollmentId) {
  if (!confirm(`Are you sure you want to approve the enrollment "${enrollmentId}"?`)) {
      return;
  }

  try {
      const response = await fetch(`/api/enrollments/${encodeURIComponent(enrollmentId)}/approve`, {
          method: 'POST'
      });

      const data = await response.json();

      if (data.error) {
          alert('Error: ' + data.error);
      } else {
          await loadEnrollments();
          await loadPersistedOtpHistory();
      }
  } catch (err) {
      alert('Error approving enrollment: ' + err.message);
  }
}

async function revokeEnrollment(enrollmentId) {
  if (!confirm(`Are you sure you want to revoke the enrollment "${enrollmentId}"?`)) {
      return;
  }

  try {
      const response = await fetch(`/api/enrollments/${encodeURIComponent(enrollmentId)}/revoke`, {
          method: 'POST'
      });

      const data = await response.json();

      if (data.error) {
          alert('Error: ' + data.error);
      } else {
          await loadEnrollments();
          await loadPersistedOtpHistory();
      }
  } catch (err) {
      alert('Error revoking enrollment: ' + err.message);
  }
}

async function denyEnrollment(enrollmentId) {
  if (!confirm(`Are you sure you want to deny the enrollment "${enrollmentId}"?`)) {
      return;
  }

  try {
      const response = await fetch(`/api/enrollments/${encodeURIComponent(enrollmentId)}/deny`, {
          method: 'POST'
      });

      const data = await response.json();

      if (data.error) {
          alert('Error: ' + data.error);
      } else {
          await loadEnrollments();
          await loadPersistedOtpHistory();
      }
  } catch (err) {
      alert('Error denying enrollment: ' + err.message);
  }
}

async function unrevokeEnrollment(enrollmentId) {
  if (!confirm(`Are you sure you want to unrevoke (restore) the enrollment "${enrollmentId}"?`)) {
      return;
  }

  try {
      const response = await fetch(`/api/enrollments/${encodeURIComponent(enrollmentId)}/unrevoke`, {
          method: 'POST'
      });

      const data = await response.json();

      if (data.error) {
          alert('Error: ' + data.error);
      } else {
          await loadEnrollments();
          await loadPersistedOtpHistory();
      }
  } catch (err) {
      alert('Error unrevoking enrollment: ' + err.message);
  }
}

async function deleteEnrollment(enrollmentId) {
  if (!confirm(`Are you sure you want to DELETE the enrollment "${enrollmentId}"?\n\nThis will delete all associated APKAM keys and cannot be undone.`)) {
      return;
  }

  try {
      const response = await fetch(`/api/enrollments/${encodeURIComponent(enrollmentId)}`, {
          method: 'DELETE'
      });

      const data = await response.json();

      if (data.error) {
          alert('Error: ' + data.error);
      } else {
          await loadEnrollments();
          await loadPersistedOtpHistory();
      }
  } catch (err) {
      alert('Error deleting enrollment: ' + err.message);
  }
}

function escapeHtml(text) {
  const div = document.createElement('div');
  div.textContent = text;
  return div.innerHTML;
}

function escapeJsString(value) {
  return String(value || '')
      .replace(/\\/g, '\\\\')
      .replace(/'/g, "\\'")
      .replace(/\n/g, '\\n')
      .replace(/\r/g, '\\r');
}

function showUpdateNotification(eventType) {
  const notification = document.createElement('div');
  notification.className = 'toast';

  const toneMap = {
      'created': 'info',
      'approved': 'success',
      'denied': 'warning',
      'revoked': 'warning',
      'unrevoked': 'success',
      'deleted': 'error',
      'status_changed': 'info'
  };
  const tone = toneMap[eventType] || 'info';
  notification.classList.add('toast-' + tone);

  const messages = {
      'created': 'New enrollment detected',
      'approved': 'Enrollment approved',
      'denied': 'Enrollment denied',
      'revoked': 'Enrollment revoked',
      'unrevoked': 'Enrollment restored',
      'deleted': 'Enrollment deleted',
      'status_changed': 'Enrollment status changed'
  };

  notification.textContent = messages[eventType] || 'Enrollment updated';
  document.body.appendChild(notification);

  requestAnimationFrame(() => {
      notification.classList.add('toast-visible');
  });

  setTimeout(() => {
      notification.classList.remove('toast-visible');
      notification.classList.add('toast-exit');
      notification.addEventListener('animationend', () => {
          if (notification.parentNode) {
              notification.parentNode.removeChild(notification);
          }
      });
  }, 3000);
}

// Wizards menu functions
function toggleWizardsMenu() {
  const menu = document.getElementById('wizards-menu');
  if (menu) {
      menu.classList.toggle('show');
  }
}

function closeWizardsMenu() {
  const menu = document.getElementById('wizards-menu');
  if (menu) {
      menu.classList.remove('show');
  }
}

// Go back to atSign selection
async function goBack() {
  // Close event stream
  if (eventSource) {
      eventSource.close();
  }
  if (autoApprovalRefreshTimer) {
      window.clearInterval(autoApprovalRefreshTimer);
      autoApprovalRefreshTimer = null;
  }

  // Navigate to landing page (which is shown when no atSign is selected)
  window.location.href = '/landing.html';
}

// Close server with confirmation
async function closeServer() {
  const confirmed = confirm('Are you sure you want to shutdown the server?\n\nThis will:\n• Stop the web server\n• Close this browser tab\n• Exit the at_activate_web application');

  if (!confirmed) {
      return;
  }

  try {
      // Send shutdown request to server
      const response = await fetch('/api/shutdown', {
          method: 'POST'
      });

      if (response.ok) {
          // Show shutdown message
          document.body.innerHTML = '<div class="shutdown-screen"><div class="shutdown-title">Server shutting down...</div><div class="shutdown-subtitle">You can close this tab</div></div>';

          // Close event stream
          if (eventSource) {
              eventSource.close();
          }

          // Wait a moment then close the tab
          setTimeout(() => {
              window.close();
          }, 1500);
      } else {
          alert('Failed to shutdown server. You may need to stop it manually (Ctrl+C in terminal).');
      }
  } catch (error) {
      console.error('Error shutting down server:', error);
      alert('Error sending shutdown request. You may need to stop the server manually (Ctrl+C in terminal).');
  }
}

document.addEventListener('DOMContentLoaded', function () {
  const deviceNameInput = document.getElementById('enroll-device-name');
  const appNameInput = document.getElementById('enroll-app-name');

  if (deviceNameInput) {
      deviceNameInput.addEventListener('input', function () {
          setEnrollmentCommandResult();
      });
  }

  if (appNameInput) {
      appNameInput.addEventListener('input', function () {
          setEnrollmentCommandResult();
      });
  }

  applyEnrollmentProfile('noports');
  loadPersistedOtpHistory();
  loadAutoApprovalServices();
  autoApprovalRefreshTimer = window.setInterval(loadAutoApprovalServices, 5000);
});

loadEnrollments();
initializeEventStream();
''';