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();
''';