Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>RemoteDroid - Node View</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/d3@7"></script> | |
| <style> | |
| body { | |
| background-color: #111; | |
| background-image: | |
| linear-gradient(#333 1px, transparent 1px), | |
| linear-gradient(90deg, #333 1px, transparent 1px); | |
| background-size: 40px 40px; | |
| } | |
| .device-frame { | |
| background: #222; | |
| border-radius: 20px; | |
| box-shadow: 0 10px 30px rgba(0,0,0,0.5); | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .device-screen { | |
| background: #000; | |
| width: 100%; | |
| height: 100%; | |
| overflow: hidden; | |
| } | |
| .node-container { | |
| background: rgba(30, 30, 30, 0.9); | |
| border: 1px solid #444; | |
| transition: all 0.3s ease; | |
| } | |
| .connection-line { | |
| stroke: #6366f1; | |
| stroke-width: 2; | |
| opacity: 0.6; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-900 text-white min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="flex justify-between items-center mb-8"> | |
| <div> | |
| <h1 class="text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-purple-400 to-pink-600"> | |
| Node View | |
| </h1> | |
| <p class="text-gray-300">Visualize connected device network</p> | |
| </div> | |
| <div class="flex items-center space-x-4"> | |
| <a href="index.html" class="bg-purple-600 hover:bg-purple-700 px-6 py-2 rounded-full flex items-center"> | |
| <i data-feather="arrow-left" class="mr-2"></i> Back to Control Hub | |
| </a> | |
| </div> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-4 gap-6"> | |
| <!-- Sidebar --> | |
| <div class="lg:col-span-1"> | |
| <div class="bg-gray-800 bg-opacity-50 backdrop-blur-md rounded-xl p-6 mb-6"> | |
| <h2 class="text-lg font-semibold mb-4 flex items-center"> | |
| <i data-feather="cpu" class="mr-2"></i> Active Devices | |
| </h2> | |
| <div class="space-y-3" id="device-list"> | |
| <!-- Device list will be populated by JavaScript --> | |
| </div> | |
| </div> | |
| <div class="bg-gray-800 bg-opacity-50 backdrop-blur-md rounded-xl p-6"> | |
| <h2 class="text-lg font-semibold mb-4 flex items-center"> | |
| <i data-feather="settings" class="mr-2"></i> Controls | |
| </h2> | |
| <div class="space-y-4"> | |
| <button id="add-device" class="w-full bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded-md flex items-center justify-center"> | |
| <i data-feather="plus" class="mr-2"></i> Add Simulated Device | |
| </button> | |
| <button id="refresh-network" class="w-full bg-gray-700 hover:bg-gray-600 px-4 py-2 rounded-md flex items-center justify-center"> | |
| <i data-feather="refresh-cw" class="mr-2"></i> Refresh | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main Visualization --> | |
| <div class="lg:col-span-3"> | |
| <div class="rounded-xl p-6 h-full"> | |
| <h2 class="text-lg font-semibold mb-4">Device Workspace</h2> | |
| <div id="node-visualization" class="w-full h-96 lg:h-auto" style="min-height: 500px;"> | |
| <svg class="w-full h-full" id="network-svg"></svg> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Device Details Modal --> | |
| <div id="device-modal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden"> | |
| <div class="flex items-center justify-center h-full"> | |
| <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full mx-4"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h3 class="text-lg font-semibold">Device Details</h3> | |
| <button id="close-modal" class="text-gray-400 hover:text-white"> | |
| <i data-feather="x"></i> | |
| </button> | |
| </div> | |
| <div id="device-details" class="space-y-3"> | |
| <!-- Details will be populated by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Initial devices data | |
| let devices = [ | |
| { id: 1, name: "Pixel 6", ip: "192.168.1.101", status: "active", battery: 87, type: "android", lastSync: "2 min ago" }, | |
| { id: 2, name: "Galaxy S22", ip: "192.168.1.102", status: "active", battery: 64, type: "android", lastSync: "1 min ago" }, | |
| { id: 3, name: "iPhone 14", ip: "192.168.1.103", status: "idle", battery: 92, type: "ios", lastSync: "5 min ago" }, | |
| { id: 4, name: "Xperia 5", ip: "192.168.1.104", status: "active", battery: 53, type: "android", lastSync: "3 min ago" } | |
| ]; | |
| // D3.js Network Visualization | |
| const width = document.getElementById('network-svg').clientWidth; | |
| const height = document.getElementById('network-svg').clientHeight || 500; | |
| const svg = d3.select('#network-svg') | |
| .attr('viewBox', `0 0 ${width} ${height}`); | |
| // Create force simulation | |
| const simulation = d3.forceSimulation() | |
| .force('link', d3.forceLink().id(d => d.id).distance(250)) | |
| .force('charge', d3.forceManyBody().strength(-200)) | |
| .force('center', d3.forceCenter(width / 2, height / 2)) | |
| .force('collision', d3.forceCollide().radius(d => d.type === 'hub' ? 100 : 80)); | |
| // Initialize network | |
| function initNetwork() { | |
| // Clear existing | |
| svg.selectAll('*').remove(); | |
| const nodes = devices.map(d => ({ ...d })); | |
| const links = []; | |
| // Create links between devices and central hub | |
| devices.forEach(d => { | |
| links.push({ source: 'hub', target: d.id }); | |
| }); | |
| nodes.push({ id: 'hub', name: 'Control Hub', type: 'hub' }); | |
| // Create container | |
| const g = svg.append('g'); | |
| // Create links | |
| const link = g.append('g') | |
| .selectAll('line') | |
| .data(links) | |
| .enter().append('line') | |
| .attr('class', 'connection-line'); | |
| // Create nodes | |
| const node = g.append('g') | |
| .selectAll('g') | |
| .data(nodes) | |
| .enter().append('g') | |
| .attr('class', 'node-container') | |
| .call(d3.drag() | |
| .on('start', dragstarted) | |
| .on('drag', dragged) | |
| .on('end', dragended)); | |
| // Add device frames | |
| node.append('foreignObject') | |
| .attr('width', 160) | |
| .attr('height', 300) | |
| .attr('x', -80) | |
| .attr('y', -150) | |
| .append('xhtml:div') | |
| .attr('class', 'device-frame') | |
| .style('width', '160px') | |
| .style('height', '300px') | |
| .html(d => { | |
| if (d.type === 'hub') return `<div class="device-screen flex items-center justify-center text-white">Control Hub</div>`; | |
| return ` | |
| <div class="device-screen"> | |
| <img src="http://static.photos/technology/160x300/${d.id}" class="w-full h-full object-cover" alt="Device screen"> | |
| </div> | |
| `; | |
| }) | |
| .on('click', (event, d) => showDeviceDetails(d)); | |
| // Add device labels | |
| node.append('text') | |
| .attr('dy', 170) | |
| .attr('text-anchor', 'middle') | |
| .attr('fill', '#e5e7eb') | |
| .style('font-size', '12px') | |
| .text(d => d.name || d.id); | |
| simulation.nodes(nodes); | |
| simulation.force('link').links(links); | |
| simulation.on('tick', () => { | |
| link | |
| .attr('x1', d => d.source.x) | |
| .attr('y1', d => d.source.y) | |
| .attr('x2', d => d.target.x) | |
| .attr('y2', d => d.target.y); | |
| node | |
| .attr('transform', d => `translate(${d.x},${d.y})`); | |
| }); | |
| } | |
| // Drag functions | |
| function dragstarted(event) { | |
| if (!event.active) simulation.alphaTarget(0.3).restart(); | |
| event.subject.fx = event.subject.x; | |
| event.subject.fy = event.subject.y; | |
| } | |
| function dragged(event) { | |
| event.subject.fx = event.x; | |
| event.subject.fy = event.y; | |
| } | |
| function dragended(event) { | |
| if (!event.active) simulation.alphaTarget(0); | |
| event.subject.fx = null; | |
| event.subject.fy = null; | |
| } | |
| // Functions for UI interactions | |
| function renderDeviceList() { | |
| const deviceList = document.getElementById('device-list'); | |
| deviceList.innerHTML = ''; | |
| devices.forEach(device => { | |
| const div = document.createElement('div'); | |
| div.className = 'flex items-center justify-between p-2 rounded hover:bg-gray-700 cursor-pointer'; | |
| div.innerHTML = ` | |
| <div class="flex items-center"> | |
| <span class="mr-2">${device.type === 'ios' ? 'π' : 'π€'}</span> | |
| <span class="text-sm">${device.name}</span> | |
| </div> | |
| <div class="w-3 h-3 rounded-full ${device.status === 'active' ? 'bg-green-500' : 'bg-gray-500'}"></div> | |
| `; | |
| deviceList.appendChild(div); | |
| }); | |
| } | |
| function showDeviceDetails(device) { | |
| if (device.id === 'hub') return; | |
| const modal = document.getElementById('device-modal'); | |
| const details = document.getElementById('device-details'); | |
| details.innerHTML = ` | |
| <div> | |
| <span class="text-gray-400">Name:</span> | |
| <span>${device.name}</span> | |
| </div> | |
| <div> | |
| <span class="text-gray-400">IP Address:</span> | |
| <span>${device.ip}</span> | |
| </div> | |
| <div> | |
| <span class="text-gray-400">Battery:</span> | |
| <span>${device.battery}%</span> | |
| </div> | |
| <div> | |
| <span class="text-gray-400">Status:</span> | |
| <span class="${device.status === 'active' ? 'text-green-400' : 'text-gray-400'}">${device.status}</span> | |
| </div> | |
| <div> | |
| <span class="text-gray-400">Last Sync:</span> | |
| <span>${device.lastSync}</span> | |
| </div> | |
| <button class="w-full bg-purple-600 hover:bg-purple-700 px-4 py-2 rounded-md mt-4"> | |
| Connect to this device | |
| </button> | |
| `; | |
| modal.classList.remove('hidden'); | |
| } | |
| // Event listeners | |
| document.getElementById('add-device').addEventListener('click', () => { | |
| const newDevice = { | |
| id: devices.length + 1, | |
| name: `Device ${devices.length + 1}`, | |
| ip: `192.168.1.${100 + devices.length + 1}`, | |
| status: 'active', | |
| battery: Math.floor(Math.random() * 100), | |
| type: Math.random() > 0.3 ? 'android' : 'ios', | |
| lastSync: 'Just now' | |
| }; | |
| devices.push(newDevice); | |
| renderDeviceList(); | |
| initNetwork(); | |
| }); | |
| document.getElementById('refresh-network').addEventListener('click', () => { | |
| initNetwork(); | |
| }); | |
| document.getElementById('close-modal').addEventListener('click', () => { | |
| document.getElementById('device-modal').classList.add('hidden'); | |
| }); | |
| // Initialize | |
| renderDeviceList(); | |
| initNetwork(); | |
| feather.replace(); | |
| </script> | |
| </body> | |
| </html> |