Download frontend/index.html from OpenPeerAI/CancerAtHomeV2: direct link, hf CLI and curl.
- Browser
- Download file 19.8 kB
-
https://huggingface.co/OpenPeerAI/CancerAtHomeV2/resolve/main/frontend/index.html
- Command line
-
hf download hf://OpenPeerAI/CancerAtHomeV2/frontend/index.html
-
curl -L -o index.html https://huggingface.co/OpenPeerAI/CancerAtHomeV2/resolve/main/frontend/index.html
19.8 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Cancer@Home v2 - Dashboard</title> | |
| <script src="https://cdn.jsdelivr.net/npm/d3@7"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0"></script> | |
| <style> | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: #333; | |
| min-height: 100vh; | |
| } | |
| .header { | |
| background: rgba(0, 0, 0, 0.2); | |
| color: white; | |
| padding: 20px; | |
| text-align: center; | |
| backdrop-filter: blur(10px); | |
| } | |
| .header h1 { | |
| font-size: 2.5em; | |
| margin-bottom: 10px; | |
| } | |
| .header p { | |
| opacity: 0.9; | |
| } | |
| .container { | |
| max-width: 1400px; | |
| margin: 20px auto; | |
| padding: 0 20px; | |
| } | |
| .tabs { | |
| display: flex; | |
| gap: 10px; | |
| margin-bottom: 20px; | |
| flex-wrap: wrap; | |
| } | |
| .tab-button { | |
| background: rgba(255, 255, 255, 0.9); | |
| border: none; | |
| padding: 15px 30px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| font-weight: 500; | |
| transition: all 0.3s; | |
| } | |
| .tab-button:hover { | |
| background: white; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); | |
| } | |
| .tab-button.active { | |
| background: white; | |
| box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| } | |
| .cards { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); | |
| gap: 20px; | |
| margin-bottom: 30px; | |
| } | |
| .card { | |
| background: white; | |
| border-radius: 12px; | |
| padding: 25px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .card h3 { | |
| color: #667eea; | |
| margin-bottom: 15px; | |
| font-size: 1.3em; | |
| } | |
| .stat { | |
| font-size: 2.5em; | |
| font-weight: bold; | |
| color: #764ba2; | |
| margin: 10px 0; | |
| } | |
| .graph-container { | |
| background: white; | |
| border-radius: 12px; | |
| padding: 25px; | |
| margin-bottom: 20px; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| #neo4j-viz { | |
| width: 100%; | |
| height: 600px; | |
| border: 2px solid #e0e0e0; | |
| border-radius: 8px; | |
| } | |
| .button { | |
| background: #667eea; | |
| color: white; | |
| border: none; | |
| padding: 12px 24px; | |
| border-radius: 6px; | |
| cursor: pointer; | |
| font-size: 16px; | |
| transition: background 0.3s; | |
| } | |
| .button:hover { | |
| background: #5568d3; | |
| } | |
| .task-list { | |
| list-style: none; | |
| } | |
| .task-item { | |
| background: #f5f5f5; | |
| padding: 15px; | |
| margin: 10px 0; | |
| border-radius: 6px; | |
| border-left: 4px solid #667eea; | |
| } | |
| .task-item.completed { | |
| border-left-color: #4caf50; | |
| } | |
| .task-item.running { | |
| border-left-color: #ff9800; | |
| } | |
| .status-badge { | |
| display: inline-block; | |
| padding: 4px 12px; | |
| border-radius: 12px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| text-transform: uppercase; | |
| } | |
| .status-pending { background: #ffc107; color: #000; } | |
| .status-running { background: #2196f3; color: white; } | |
| .status-completed { background: #4caf50; color: white; } | |
| .status-failed { background: #f44336; color: white; } | |
| .input-group { | |
| margin: 15px 0; | |
| } | |
| .input-group label { | |
| display: block; | |
| margin-bottom: 5px; | |
| font-weight: 500; | |
| } | |
| .input-group input, .input-group select { | |
| width: 100%; | |
| padding: 10px; | |
| border: 1px solid #ddd; | |
| border-radius: 6px; | |
| font-size: 14px; | |
| } | |
| .project-card { | |
| background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); | |
| color: white; | |
| padding: 20px; | |
| border-radius: 8px; | |
| margin: 10px 0; | |
| cursor: pointer; | |
| transition: transform 0.2s; | |
| } | |
| .project-card:hover { | |
| transform: translateY(-3px); | |
| } | |
| .loading { | |
| text-align: center; | |
| padding: 40px; | |
| color: #667eea; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| .spinner { | |
| border: 4px solid #f3f3f3; | |
| border-top: 4px solid #667eea; | |
| border-radius: 50%; | |
| width: 40px; | |
| height: 40px; | |
| animation: spin 1s linear infinite; | |
| margin: 20px auto; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>🧬 Cancer@Home v2</h1> | |
| <p>Distributed Cancer Genomics Research Platform</p> | |
| </div> | |
| <div class="container"> | |
| <div class="tabs"> | |
| <button class="tab-button active" onclick="showTab('dashboard')">📊 Dashboard</button> | |
| <button class="tab-button" onclick="showTab('neo4j')">🔍 Neo4j Visualization</button> | |
| <button class="tab-button" onclick="showTab('boinc')">⚡ BOINC Tasks</button> | |
| <button class="tab-button" onclick="showTab('gdc')">📚 GDC Data</button> | |
| <button class="tab-button" onclick="showTab('pipeline')">🧪 Analysis Pipeline</button> | |
| </div> | |
| <!-- Dashboard Tab --> | |
| <div id="dashboard" class="tab-content active"> | |
| <div class="cards" id="stats-cards"> | |
| <div class="card"> | |
| <h3>Total Genes</h3> | |
| <div class="stat" id="total-genes">-</div> | |
| </div> | |
| <div class="card"> | |
| <h3>Total Mutations</h3> | |
| <div class="stat" id="total-mutations">-</div> | |
| </div> | |
| <div class="card"> | |
| <h3>Total Patients</h3> | |
| <div class="stat" id="total-patients">-</div> | |
| </div> | |
| <div class="card"> | |
| <h3>Cancer Types</h3> | |
| <div class="stat" id="total-cancer-types">-</div> | |
| </div> | |
| </div> | |
| <div class="graph-container"> | |
| <h3>Mutation Distribution by Cancer Type</h3> | |
| <canvas id="mutation-chart"></canvas> | |
| </div> | |
| </div> | |
| <!-- Neo4j Visualization Tab --> | |
| <div id="neo4j" class="tab-content"> | |
| <div class="graph-container"> | |
| <h3>Cancer Genomics Knowledge Graph</h3> | |
| <div id="neo4j-viz"></div> | |
| </div> | |
| </div> | |
| <!-- BOINC Tasks Tab --> | |
| <div id="boinc" class="tab-content"> | |
| <div class="cards"> | |
| <div class="card"> | |
| <h3>Submit New Task</h3> | |
| <div class="input-group"> | |
| <label>Task Type</label> | |
| <select id="task-type"> | |
| <option value="variant_calling">Variant Calling</option> | |
| <option value="blast_search">BLAST Search</option> | |
| <option value="alignment">Sequence Alignment</option> | |
| </select> | |
| </div> | |
| <div class="input-group"> | |
| <label>Input File</label> | |
| <input type="text" id="input-file" placeholder="path/to/input.fastq"> | |
| </div> | |
| <button class="button" onclick="submitBoincTask()">Submit Task</button> | |
| </div> | |
| <div class="card"> | |
| <h3>BOINC Statistics</h3> | |
| <div id="boinc-stats"></div> | |
| </div> | |
| </div> | |
| <div class="card"> | |
| <h3>Active Tasks</h3> | |
| <ul class="task-list" id="task-list"></ul> | |
| </div> | |
| </div> | |
| <!-- GDC Data Tab --> | |
| <div id="gdc" class="tab-content"> | |
| <div class="card"> | |
| <h3>Available GDC Projects</h3> | |
| <div id="gdc-projects"></div> | |
| </div> | |
| </div> | |
| <!-- Pipeline Tab --> | |
| <div id="pipeline" class="tab-content"> | |
| <div class="cards"> | |
| <div class="card"> | |
| <h3>FASTQ Quality Control</h3> | |
| <p>Run quality control analysis on sequencing data</p> | |
| <button class="button" style="margin-top: 15px;">Run QC</button> | |
| </div> | |
| <div class="card"> | |
| <h3>BLAST Search</h3> | |
| <p>Perform sequence alignment and homology search</p> | |
| <button class="button" style="margin-top: 15px;">Run BLAST</button> | |
| </div> | |
| <div class="card"> | |
| <h3>Variant Calling</h3> | |
| <p>Identify genetic variants from sequencing data</p> | |
| <button class="button" style="margin-top: 15px;">Call Variants</button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Tab switching | |
| function showTab(tabName) { | |
| document.querySelectorAll('.tab-content').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| document.querySelectorAll('.tab-button').forEach(btn => { | |
| btn.classList.remove('active'); | |
| }); | |
| document.getElementById(tabName).classList.add('active'); | |
| event.target.classList.add('active'); | |
| if (tabName === 'dashboard') loadDashboard(); | |
| else if (tabName === 'neo4j') loadNeo4jViz(); | |
| else if (tabName === 'boinc') loadBoincTasks(); | |
| else if (tabName === 'gdc') loadGdcProjects(); | |
| } | |
| // Load dashboard data | |
| async function loadDashboard() { | |
| try { | |
| const response = await fetch('/api/neo4j/summary'); | |
| const data = await response.json(); | |
| document.getElementById('total-genes').textContent = data.genes || 0; | |
| document.getElementById('total-mutations').textContent = data.mutations || 0; | |
| document.getElementById('total-patients').textContent = data.patients || 0; | |
| document.getElementById('total-cancer-types').textContent = data.cancer_types || 0; | |
| createMutationChart(); | |
| } catch (error) { | |
| console.error('Error loading dashboard:', error); | |
| } | |
| } | |
| // Create mutation chart | |
| function createMutationChart() { | |
| const ctx = document.getElementById('mutation-chart').getContext('2d'); | |
| new Chart(ctx, { | |
| type: 'bar', | |
| data: { | |
| labels: ['Breast Cancer', 'Lung Adenocarcinoma', 'Colon Adenocarcinoma', 'Glioblastoma'], | |
| datasets: [{ | |
| label: 'Mutations', | |
| data: [245, 189, 156, 203], | |
| backgroundColor: [ | |
| 'rgba(102, 126, 234, 0.8)', | |
| 'rgba(118, 75, 162, 0.8)', | |
| 'rgba(237, 100, 166, 0.8)', | |
| 'rgba(255, 154, 158, 0.8)' | |
| ] | |
| }] | |
| }, | |
| options: { | |
| responsive: true, | |
| maintainAspectRatio: true, | |
| plugins: { | |
| legend: { display: false } | |
| } | |
| } | |
| }); | |
| } | |
| // Load Neo4j visualization | |
| function loadNeo4jViz() { | |
| const viz = document.getElementById('neo4j-viz'); | |
| viz.innerHTML = '<div class="loading"><div class="spinner"></div><p>Loading graph visualization...</p></div>'; | |
| // Simulate graph visualization with D3.js | |
| setTimeout(() => { | |
| const width = viz.clientWidth; | |
| const height = 600; | |
| viz.innerHTML = ''; | |
| const svg = d3.select('#neo4j-viz') | |
| .append('svg') | |
| .attr('width', width) | |
| .attr('height', height); | |
| // Sample data | |
| const nodes = [ | |
| { id: 'TP53', type: 'gene', x: width/2, y: height/2 }, | |
| { id: 'BRCA1', type: 'gene', x: width/3, y: height/3 }, | |
| { id: 'KRAS', type: 'gene', x: 2*width/3, y: height/3 }, | |
| { id: 'Patient 1', type: 'patient', x: width/4, y: 3*height/4 }, | |
| { id: 'Patient 2', type: 'patient', x: 3*width/4, y: 3*height/4 }, | |
| { id: 'Breast Cancer', type: 'cancer', x: width/2, y: height/4 } | |
| ]; | |
| const links = [ | |
| { source: 'Patient 1', target: 'TP53' }, | |
| { source: 'Patient 1', target: 'Breast Cancer' }, | |
| { source: 'Patient 2', target: 'KRAS' }, | |
| { source: 'TP53', target: 'Breast Cancer' } | |
| ]; | |
| // Draw links | |
| svg.selectAll('line') | |
| .data(links) | |
| .enter() | |
| .append('line') | |
| .attr('x1', d => nodes.find(n => n.id === d.source).x) | |
| .attr('y1', d => nodes.find(n => n.id === d.source).y) | |
| .attr('x2', d => nodes.find(n => n.id === d.target).x) | |
| .attr('y2', d => nodes.find(n => n.id === d.target).y) | |
| .attr('stroke', '#999') | |
| .attr('stroke-width', 2); | |
| // Draw nodes | |
| svg.selectAll('circle') | |
| .data(nodes) | |
| .enter() | |
| .append('circle') | |
| .attr('cx', d => d.x) | |
| .attr('cy', d => d.y) | |
| .attr('r', 20) | |
| .attr('fill', d => { | |
| if (d.type === 'gene') return '#667eea'; | |
| if (d.type === 'patient') return '#764ba2'; | |
| return '#ed64a6'; | |
| }); | |
| // Draw labels | |
| svg.selectAll('text') | |
| .data(nodes) | |
| .enter() | |
| .append('text') | |
| .attr('x', d => d.x) | |
| .attr('y', d => d.y - 25) | |
| .attr('text-anchor', 'middle') | |
| .text(d => d.id) | |
| .attr('font-size', '12px') | |
| .attr('fill', '#333'); | |
| }, 500); | |
| } | |
| // Load BOINC tasks | |
| async function loadBoincTasks() { | |
| try { | |
| const [tasksResponse, statsResponse] = await Promise.all([ | |
| fetch('/api/boinc/tasks'), | |
| fetch('/api/boinc/statistics') | |
| ]); | |
| const tasksData = await tasksResponse.json(); | |
| const statsData = await statsResponse.json(); | |
| // Display tasks | |
| const taskList = document.getElementById('task-list'); | |
| taskList.innerHTML = tasksData.tasks.map(task => ` | |
| <li class="task-item ${task.status}"> | |
| <strong>${task.name}</strong> | |
| <span class="status-badge status-${task.status}">${task.status}</span> | |
| <div style="margin-top: 8px; font-size: 14px; color: #666;"> | |
| Type: ${task.workunit_type} | Created: ${new Date(task.created_at).toLocaleString()} | |
| </div> | |
| </li> | |
| `).join(''); | |
| // Display statistics | |
| const statsDiv = document.getElementById('boinc-stats'); | |
| statsDiv.innerHTML = ` | |
| <p><strong>Total Tasks:</strong> ${statsData.total_tasks}</p> | |
| <p><strong>Completed:</strong> ${statsData.by_status?.completed || 0}</p> | |
| <p><strong>Running:</strong> ${statsData.by_status?.running || 0}</p> | |
| <p><strong>Pending:</strong> ${statsData.by_status?.pending || 0}</p> | |
| `; | |
| } catch (error) { | |
| console.error('Error loading BOINC tasks:', error); | |
| } | |
| } | |
| // Submit BOINC task | |
| async function submitBoincTask() { | |
| const taskType = document.getElementById('task-type').value; | |
| const inputFile = document.getElementById('input-file').value; | |
| if (!inputFile) { | |
| alert('Please provide an input file path'); | |
| return; | |
| } | |
| try { | |
| const response = await fetch('/api/boinc/submit', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ workunit_type: taskType, input_file: inputFile }) | |
| }); | |
| const data = await response.json(); | |
| alert(`Task submitted successfully! Task ID: ${data.task_id}`); | |
| loadBoincTasks(); | |
| } catch (error) { | |
| console.error('Error submitting task:', error); | |
| alert('Failed to submit task'); | |
| } | |
| } | |
| // Load GDC projects | |
| async function loadGdcProjects() { | |
| try { | |
| const response = await fetch('/api/gdc/projects'); | |
| const data = await response.json(); | |
| const projectsDiv = document.getElementById('gdc-projects'); | |
| projectsDiv.innerHTML = data.projects.map(project => ` | |
| <div class="project-card"> | |
| <h4>${project.name}</h4> | |
| <p>Project ID: ${project.id}</p> | |
| <p>Cases: ${project.cases}</p> | |
| </div> | |
| `).join(''); | |
| } catch (error) { | |
| console.error('Error loading GDC projects:', error); | |
| } | |
| } | |
| // Initialize dashboard on load | |
| window.onload = () => { | |
| loadDashboard(); | |
| }; | |
| </script> | |
| </body> | |
| </html> | |