// API Base URL const API_BASE = '/api/v1'; // Pagination let currentPage = 1; let pageSize = 25; let totalArtifacts = 0; // Auto-refresh let autoRefreshEnabled = true; let autoRefreshInterval = null; const REFRESH_INTERVAL_MS = 5000; // 5 seconds // Sorting and filtering let allArtifacts = []; // Store all artifacts for client-side sorting/filtering let currentSortColumn = null; let currentSortDirection = 'asc'; // Load API info on page load window.addEventListener('DOMContentLoaded', () => { loadApiInfo(); loadArtifacts(); startAutoRefresh(); }); // Load API information async function loadApiInfo() { try { const response = await fetch('/api'); const data = await response.json(); document.getElementById('deployment-mode').textContent = `Mode: ${data.deployment_mode}`; document.getElementById('storage-backend').textContent = `Storage: ${data.storage_backend}`; } catch (error) { console.error('Error loading API info:', error); } } // Load artifacts async function loadArtifacts(limit = pageSize, offset = 0) { try { const response = await fetch(`${API_BASE}/artifacts/?limit=${limit}&offset=${offset}`); const artifacts = await response.json(); allArtifacts = artifacts; // Store for sorting/filtering displayArtifacts(artifacts); updatePagination(artifacts.length); } catch (error) { console.error('Error loading artifacts:', error); document.getElementById('artifacts-tbody').innerHTML = ` Error loading artifacts: ${error.message} `; } } // Display artifacts in table function displayArtifacts(artifacts) { const tbody = document.getElementById('artifacts-tbody'); if (artifacts.length === 0) { tbody.innerHTML = 'No artifacts found. Upload some files to get started!'; document.getElementById('artifact-count').textContent = '0 artifacts'; return; } // Apply current sort if active let displayedArtifacts = artifacts; if (currentSortColumn) { displayedArtifacts = applySorting([...artifacts]); } tbody.innerHTML = displayedArtifacts.map(artifact => ` ${artifact.sim_source_id || artifact.test_suite || '-'} ${escapeHtml(artifact.filename)} ${artifact.tags && artifact.tags.length > 0 ? `
${formatTags(artifact.tags)}
` : ''} ${formatDate(artifact.created_at)} ${artifact.test_name || '-'}
`).join(''); document.getElementById('artifact-count').textContent = `${displayedArtifacts.length} artifacts`; // Re-initialize Lucide icons for dynamically added content lucide.createIcons(); } // Format result badge function formatResult(result) { if (!result) return '-'; const className = `result-badge result-${result}`; return `${result}`; } // Format tags function formatTags(tags) { if (!tags || tags.length === 0) return '-'; return tags.map(tag => `${escapeHtml(tag)}`).join(' '); } // Format bytes function formatBytes(bytes) { if (bytes === 0) return '0 B'; const k = 1024; const sizes = ['B', 'KB', 'MB', 'GB']; const i = Math.floor(Math.log(bytes) / Math.log(k)); return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; } // Format date function formatDate(dateString) { const date = new Date(dateString); return date.toLocaleString(); } // Escape HTML function escapeHtml(text) { if (!text) return ''; const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Show artifact detail async function showDetail(id) { try { const response = await fetch(`${API_BASE}/artifacts/${id}`); const artifact = await response.json(); const detailContent = document.getElementById('detail-content'); detailContent.innerHTML = `
ID
${artifact.id}
Filename
${escapeHtml(artifact.filename)}
File Type
${artifact.file_type}
Size
${formatBytes(artifact.file_size)}
Storage Path
${artifact.storage_path}
Uploaded By
${artifact.test_name || '-'}
Sim Source
${artifact.test_suite || '-'}
Test Result
${formatResult(artifact.test_result)}
${artifact.test_config ? `
Test Config
${JSON.stringify(artifact.test_config, null, 2)}
` : ''} ${artifact.custom_metadata ? `
Custom Metadata
${JSON.stringify(artifact.custom_metadata, null, 2)}
` : ''} ${artifact.description ? `
Description
${escapeHtml(artifact.description)}
` : ''} ${artifact.tags && artifact.tags.length > 0 ? `
Tags
${formatTags(artifact.tags)}
` : ''}
Version
${artifact.version || '-'}
Created
${formatDate(artifact.created_at)}
Updated
${formatDate(artifact.updated_at)}
`; document.getElementById('detail-modal').classList.add('active'); // Re-initialize Lucide icons for modal content lucide.createIcons(); } catch (error) { alert('Error loading artifact details: ' + error.message); } } // Close detail modal function closeDetailModal() { document.getElementById('detail-modal').classList.remove('active'); } // Download artifact async function downloadArtifact(id, filename) { try { const response = await fetch(`${API_BASE}/artifacts/${id}/download`); const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); } catch (error) { alert('Error downloading artifact: ' + error.message); } } // Delete artifact async function deleteArtifact(id) { if (!confirm('Are you sure you want to delete this artifact? This cannot be undone.')) { return; } try { const response = await fetch(`${API_BASE}/artifacts/${id}`, { method: 'DELETE' }); if (response.ok) { loadArtifacts((currentPage - 1) * pageSize, pageSize); alert('Artifact deleted successfully'); } else { throw new Error('Failed to delete artifact'); } } catch (error) { alert('Error deleting artifact: ' + error.message); } } // Upload artifact async function uploadArtifact(event) { event.preventDefault(); const form = event.target; const formData = new FormData(); // Add file const fileInput = document.getElementById('file'); formData.append('file', fileInput.files[0]); // Add optional fields const fields = ['test_name', 'test_suite', 'test_result', 'version', 'description', 'sim_source_id']; fields.forEach(field => { const value = form.elements[field]?.value; if (value) formData.append(field, value); }); // Add tags (convert comma-separated to JSON array) const tags = document.getElementById('tags').value; if (tags) { const tagsArray = tags.split(',').map(t => t.trim()).filter(t => t); formData.append('tags', JSON.stringify(tagsArray)); } // Add JSON fields const testConfig = document.getElementById('test-config').value; if (testConfig) { try { JSON.parse(testConfig); // Validate formData.append('test_config', testConfig); } catch (e) { showUploadStatus('Invalid Test Config JSON', false); return; } } const customMetadata = document.getElementById('custom-metadata').value; if (customMetadata) { try { JSON.parse(customMetadata); // Validate formData.append('custom_metadata', customMetadata); } catch (e) { showUploadStatus('Invalid Custom Metadata JSON', false); return; } } try { const response = await fetch(`${API_BASE}/artifacts/upload`, { method: 'POST', body: formData }); if (response.ok) { const artifact = await response.json(); showUploadStatus(`Successfully uploaded: ${artifact.filename}`, true); form.reset(); loadArtifacts(); } else { const error = await response.json(); throw new Error(error.detail || 'Upload failed'); } } catch (error) { showUploadStatus('Upload failed: ' + error.message, false); } } // Show upload status function showUploadStatus(message, success) { const status = document.getElementById('upload-status'); status.textContent = message; status.className = success ? 'success' : 'error'; setTimeout(() => { status.style.display = 'none'; }, 5000); } // Query artifacts async function queryArtifacts(event) { event.preventDefault(); const query = {}; const filename = document.getElementById('q-filename').value; if (filename) query.filename = filename; const fileType = document.getElementById('q-type').value; if (fileType) query.file_type = fileType; const testName = document.getElementById('q-test-name').value; if (testName) query.test_name = testName; const suite = document.getElementById('q-suite').value; if (suite) query.test_suite = suite; const result = document.getElementById('q-result').value; if (result) query.test_result = result; const tags = document.getElementById('q-tags').value; if (tags) { query.tags = tags.split(',').map(t => t.trim()).filter(t => t); } const startDate = document.getElementById('q-start-date').value; if (startDate) query.start_date = new Date(startDate).toISOString(); const endDate = document.getElementById('q-end-date').value; if (endDate) query.end_date = new Date(endDate).toISOString(); query.limit = 100; query.offset = 0; try { const response = await fetch(`${API_BASE}/artifacts/query`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(query) }); const artifacts = await response.json(); // Switch to artifacts tab and display results showTab('artifacts'); displayArtifacts(artifacts); } catch (error) { alert('Query failed: ' + error.message); } } // Clear query form function clearQuery() { document.getElementById('query-form').reset(); } // Generate seed data async function generateSeedData() { const count = prompt('How many artifacts to generate? (1-100)', '10'); if (!count) return; const num = parseInt(count); if (isNaN(num) || num < 1 || num > 100) { alert('Please enter a number between 1 and 100'); return; } try { const response = await fetch(`/api/v1/seed/generate/${num}`, { method: 'POST' }); const result = await response.json(); if (response.ok) { alert(result.message); loadArtifacts(); } else { throw new Error(result.detail || 'Generation failed'); } } catch (error) { alert('Error generating seed data: ' + error.message); } } // Tab navigation function showTab(tabName) { // Hide all tabs document.querySelectorAll('.tab-content').forEach(tab => { tab.classList.remove('active'); }); document.querySelectorAll('.tab-button').forEach(btn => { btn.classList.remove('active'); }); // Show selected tab document.getElementById(tabName + '-tab').classList.add('active'); event.target.classList.add('active'); } // Pagination function updatePagination(count) { const pageInfo = document.getElementById('page-info'); pageInfo.textContent = `Page ${currentPage}`; document.getElementById('prev-btn').disabled = currentPage === 1; document.getElementById('next-btn').disabled = count < pageSize; } function previousPage() { if (currentPage > 1) { currentPage--; loadArtifacts(pageSize, (currentPage - 1) * pageSize); } } function nextPage() { currentPage++; loadArtifacts(pageSize, (currentPage - 1) * pageSize); } // Auto-refresh functions function startAutoRefresh() { if (autoRefreshInterval) { clearInterval(autoRefreshInterval); } if (autoRefreshEnabled) { autoRefreshInterval = setInterval(() => { // Only refresh if on the artifacts tab const artifactsTab = document.getElementById('artifacts-tab'); if (artifactsTab && artifactsTab.classList.contains('active')) { loadArtifacts(pageSize, (currentPage - 1) * pageSize); } }, REFRESH_INTERVAL_MS); } } function toggleAutoRefresh() { autoRefreshEnabled = !autoRefreshEnabled; const toggleBtn = document.getElementById('auto-refresh-toggle'); if (autoRefreshEnabled) { toggleBtn.textContent = 'Auto-refresh: ON'; toggleBtn.classList.remove('btn-secondary'); toggleBtn.classList.add('btn-success'); startAutoRefresh(); } else { toggleBtn.textContent = 'Auto-refresh: OFF'; toggleBtn.classList.remove('btn-success'); toggleBtn.classList.add('btn-secondary'); if (autoRefreshInterval) { clearInterval(autoRefreshInterval); autoRefreshInterval = null; } } } // Apply sorting to artifacts array function applySorting(artifacts) { if (!currentSortColumn) return artifacts; return artifacts.sort((a, b) => { let aVal = a[currentSortColumn] || ''; let bVal = b[currentSortColumn] || ''; // Handle date sorting if (currentSortColumn === 'created_at') { aVal = new Date(aVal).getTime(); bVal = new Date(bVal).getTime(); } else { // String comparison (case insensitive) aVal = String(aVal).toLowerCase(); bVal = String(bVal).toLowerCase(); } if (aVal < bVal) return currentSortDirection === 'asc' ? -1 : 1; if (aVal > bVal) return currentSortDirection === 'asc' ? 1 : -1; return 0; }); } // Sorting functionality function sortTable(column) { // Toggle sort direction if clicking same column if (currentSortColumn === column) { currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc'; } else { currentSortColumn = column; currentSortDirection = 'asc'; } // Update sort indicators document.querySelectorAll('th.sortable').forEach(th => { th.classList.remove('sort-asc', 'sort-desc'); }); const sortedHeader = event.target.closest('th'); sortedHeader.classList.add(`sort-${currentSortDirection}`); // Apply filter and sort filterTable(); } // Filtering functionality - searches across all columns function filterTable() { const searchTerm = document.getElementById('filter-search').value.toLowerCase(); const filteredArtifacts = allArtifacts.filter(artifact => { if (!searchTerm) return true; // Search across all relevant fields const searchableText = [ artifact.test_suite || '', artifact.filename || '', artifact.test_name || '', formatDate(artifact.created_at) ].join(' ').toLowerCase(); return searchableText.includes(searchTerm); }); displayArtifacts(filteredArtifacts); } // Clear all filters function clearFilters() { document.getElementById('filter-search').value = ''; filterTable(); }