AR / ar_dashboard.html
Gaston895's picture
Upload 13 files
54c96d3 verified
Raw
History Blame
32.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AEGIS BIO DIGITAL LAB 10 - AR Portal</title>
<meta name="description" content="AEGIS Bio Digital Lab 10 Augmented Reality Portal Dashboard">
<!-- Lucide Icons CDN -->
<script src="https://unpkg.com/lucide@latest/dist/umd/lucide.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
color: white;
min-height: 100vh;
overflow-x: hidden;
}
.header {
background: rgba(0, 0, 0, 0.4);
padding: 1.5rem 2rem;
backdrop-filter: blur(15px);
border-bottom: 1px solid rgba(0, 255, 255, 0.3);
position: sticky;
top: 0;
z-index: 100;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
background: linear-gradient(45deg, #00FFFF, #0080FF);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-align: center;
animation: pulse 2s infinite;
}
.header .subtitle {
font-size: 1.2rem;
text-align: center;
opacity: 0.9;
color: #E3F2FD;
}
.status-bar {
display: flex;
justify-content: center;
align-items: center;
gap: 2rem;
margin-top: 1rem;
flex-wrap: wrap;
}
.status-item {
display: flex;
align-items: center;
gap: 0.5rem;
font-size: 0.9rem;
opacity: 0.8;
}
.status-online {
color: #4CAF50;
}
.status-offline {
color: #f44336;
}
.main-content {
padding: 2rem;
max-width: 1400px;
margin: 0 auto;
}
.hero-section {
text-align: center;
margin-bottom: 3rem;
}
.hero-section h2 {
font-size: 2rem;
margin-bottom: 1rem;
color: #00FFFF;
}
.hero-section p {
font-size: 1.1rem;
opacity: 0.9;
max-width: 600px;
margin: 0 auto;
line-height: 1.6;
}
.portals-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
margin-bottom: 3rem;
}
.portal-card {
background: rgba(0, 0, 0, 0.4);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 15px;
padding: 1.5rem;
text-align: center;
backdrop-filter: blur(10px);
transition: all 0.3s ease;
cursor: pointer;
position: relative;
overflow: hidden;
/* Prevent layout shifts during updates */
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.portal-card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(0, 255, 255, 0.1), transparent);
transition: left 0.5s;
}
.portal-card:hover::before {
left: 100%;
}
.portal-card:hover {
transform: translateY(-5px);
border-color: rgba(0, 255, 255, 0.6);
box-shadow: 0 10px 30px rgba(0, 255, 255, 0.2);
}
/* Smooth transitions for status updates */
.status-item {
transition: all 0.3s ease;
}
.status-online {
color: #4CAF50;
transition: color 0.3s ease;
}
.status-offline {
color: #f44336;
transition: color 0.3s ease;
}
/* Prevent text jumping during updates */
.status-item span {
min-width: 150px;
display: inline-block;
text-align: left;
}
/* Smooth icon transitions */
[data-lucide] {
transition: all 0.2s ease;
}
.portal-icon {
width: 60px;
height: 60px;
margin: 0 auto 1rem;
border-radius: 12px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
z-index: 1;
}
.portal-card h3 {
font-size: 1.2rem;
margin-bottom: 0.5rem;
color: white;
position: relative;
z-index: 1;
}
.portal-card p {
font-size: 0.9rem;
opacity: 0.8;
margin-bottom: 1rem;
position: relative;
z-index: 1;
}
.launch-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: rgba(0, 255, 255, 0.2);
border: 1px solid rgba(0, 255, 255, 0.4);
border-radius: 25px;
color: #00FFFF;
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.3s ease;
position: relative;
z-index: 1;
}
.launch-btn:hover {
background: rgba(0, 255, 255, 0.3);
color: white;
}
/* Color variations for different portals */
.color-blue { background: linear-gradient(135deg, #2196F3, #1976D2); }
.color-green { background: linear-gradient(135deg, #4CAF50, #388E3C); }
.color-purple { background: linear-gradient(135deg, #9C27B0, #7B1FA2); }
.color-yellow { background: linear-gradient(135deg, #FF9800, #F57C00); }
.color-red { background: linear-gradient(135deg, #f44336, #D32F2F); }
.color-orange { background: linear-gradient(135deg, #FF5722, #E64A19); }
.color-indigo { background: linear-gradient(135deg, #3F51B5, #303F9F); }
.color-pink { background: linear-gradient(135deg, #E91E63, #C2185B); }
.color-teal { background: linear-gradient(135deg, #009688, #00796B); }
.color-cyan { background: linear-gradient(135deg, #00BCD4, #0097A7); }
.color-emerald { background: linear-gradient(135deg, #4CAF50, #2E7D32); }
.qr-section {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 15px;
padding: 2rem;
text-align: center;
margin-bottom: 2rem;
}
.qr-section h3 {
color: #00FFFF;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.qr-code-container {
margin: 1rem 0;
}
.qr-code-container img {
max-width: 200px;
border-radius: 10px;
border: 2px solid rgba(0, 255, 255, 0.3);
}
.api-section {
background: rgba(0, 0, 0, 0.3);
border: 1px solid rgba(0, 255, 255, 0.3);
border-radius: 15px;
padding: 2rem;
margin-bottom: 2rem;
}
.api-section h3 {
color: #00FFFF;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.api-endpoints {
display: grid;
gap: 0.5rem;
font-family: 'Courier New', monospace;
font-size: 0.9rem;
}
.api-endpoint {
background: rgba(0, 0, 0, 0.4);
padding: 0.75rem;
border-radius: 8px;
border-left: 3px solid #00FFFF;
}
.footer {
background: rgba(0, 0, 0, 0.4);
padding: 2rem;
text-align: center;
border-top: 1px solid rgba(0, 255, 255, 0.3);
margin-top: 2rem;
}
.footer p {
opacity: 0.7;
margin-bottom: 0.5rem;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.8; }
}
@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.animate-spin {
animation: spin 1s linear infinite;
}
.error-notification {
animation: slideIn 0.3s ease-out;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
.portal-card {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.portal-card:active {
transform: translateY(-2px) scale(0.98);
}
@media (max-width: 768px) {
.header h1 {
font-size: 1.8rem;
}
.portals-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
.status-bar {
flex-direction: column;
gap: 1rem;
}
}
</style>
</head>
<body>
<header class="header">
<h1>AEGIS BIO DIGITAL LAB 10</h1>
<div class="subtitle">AUGMENTED REALITY PORTAL DASHBOARD</div>
<div class="status-bar">
<div class="status-item">
<i data-lucide="globe"></i>
<span>10 Active Portals</span>
</div>
<div class="status-item" id="connection-status">
<i data-lucide="wifi" id="status-icon"></i>
<span id="status-text">Checking Connection...</span>
</div>
<div class="status-item">
<i data-lucide="clock"></i>
<span id="current-time"></span>
</div>
</div>
</header>
<main class="main-content">
<section class="hero-section">
<h2>🚀 Portal Access Dashboard</h2>
<p>
Welcome to the AEGIS Bio Digital Lab 10 AR Portal.
Access all system components through this unified interface.
Each portal provides specialized functionality for biological research and analysis.
</p>
</section>
<section class="portals-grid" id="portals-container">
<!-- Portals will be dynamically loaded here -->
</section>
</main>
<footer class="footer">
<p>🔬 Advanced Biological Research Platform</p>
<p>🚀 Powered by AEGIS Technology</p>
<p>&copy; 2025 AEGIS Systems - All Rights Reserved</p>
</footer>
<!-- Server-side data injection -->
<div id="server-data"
data-portals='{% if portals %}{{ portals | tojson | safe }}{% else %}[]{% endif %}'
data-base-url='{% if base_url %}{{ base_url }}{% else %}{% endif %}'
data-last-updated='{% if last_updated %}{{ last_updated }}{% else %}{% endif %}'
style="display: none;">
</div>
<script>
// Get server-side data from HTML data attributes
const serverDataElement = document.getElementById('server-data');
window.portalsData = JSON.parse(serverDataElement.getAttribute('data-portals') || '[]');
window.baseUrl = serverDataElement.getAttribute('data-base-url') || '';
window.lastUpdated = serverDataElement.getAttribute('data-last-updated') || '';
</script>
<script>
// Initialize Lucide icons
lucide.createIcons();
// Portal data with icons
const defaultPortals = [
{ name: 'AEGIS BIO DIGITAL LAB 10 TIME TRAVEL SYSTEM', url: '#', icon: 'settings', color: 'blue', description: 'Main control center' },
{ name: 'AEGIS BIO DIGITAL LAB 10 CROSS-CONTINENTAL CONTAGION', url: '#', icon: 'bar-chart-3', color: 'green', description: 'Scientific analysis tools' },
{ name: 'AEGIS BIO DIGITAL LAB 10 CONDUCTOR', url: '#', icon: 'cpu', color: 'purple', description: 'System orchestration' },
{ name: 'AEGIS Bio Digital Lab 10 ECONOMIC PROCESSOR', url: '#', icon: 'dollar-sign', color: 'yellow', description: 'Economic modeling' },
{ name: 'AEGIS BIO DIGITAL LAB 10 WAR ANALYST EXPERT', url: '#', icon: 'shield', color: 'red', description: 'Strategic analysis' },
{ name: 'AEGIS BIO DIGITAL LAB 10 DISEASE ANALYST EXPERT SYSTEM', url: '#', icon: 'bug', color: 'orange', description: 'Disease tracking' },
{ name: 'AEGIS-BIO LAB-10 VANGUARD', url: '#', icon: 'brain', color: 'indigo', description: 'AI deep analysis' },
{ name: 'AEGIS-BIO LAB-10 3D IMAGE LAB ASSISTANT', url: '#', icon: 'image', color: 'pink', description: 'Image processing' },
{ name: 'AEGIS Bio Digital Lab 10 - Drug Development System', url: '#', icon: 'pill', color: 'teal', description: 'Pharmaceutical research' },
{ name: 'AEGIS BIO DIGITAL LAB 10 Trail - Visual Kinetic System', url: '#', icon: 'eye', color: 'cyan', description: 'Visual analytics' }
];
// Update time
function updateTime() {
const now = new Date();
document.getElementById('current-time').textContent = now.toLocaleString();
}
// Global state for smooth updates
let currentPortalsData = null;
let isInitialLoad = true;
let updateTimeout = null;
// Debounced update function to prevent rapid successive updates
function debouncedUpdate(callback, delay = 500) {
if (updateTimeout) {
clearTimeout(updateTimeout);
}
updateTimeout = setTimeout(callback, delay);
}
// Load portals from API or use defaults with smooth updates
async function loadPortals(forceUpdate = false) {
const container = document.getElementById('portals-container');
// Only show loading state on initial load
if (isInitialLoad) {
container.innerHTML = `
<div style="grid-column: 1 / -1; text-align: center; padding: 2rem;">
<i data-lucide="loader-2" class="animate-spin" style="width: 48px; height: 48px; margin-bottom: 1rem;"></i>
<p>Loading portals...</p>
</div>
`;
lucide.createIcons();
}
let portalsData = null;
// Try to load from status API first
try {
const response = await fetch('/api/status');
if (response.ok) {
const data = await response.json();
if (data.base_url && data.base_url !== '') {
// Generate portals from base URL
portalsData = [
{ name: 'AEGIS BIO DIGITAL LAB 10 TIME TRAVEL SYSTEM', url: `${data.base_url}/portal`, icon: 'settings', color: 'blue', description: 'Main control center' },
{ name: 'AEGIS BIO DIGITAL LAB 10 CROSS-CONTINENTAL CONTAGION', url: `${data.base_url}/analysis`, icon: 'bar-chart-3', color: 'green', description: 'Scientific analysis tools' },
{ name: 'AEGIS BIO DIGITAL LAB 10 CONDUCTOR', url: `${data.base_url}/conductor`, icon: 'cpu', color: 'purple', description: 'System orchestration' },
{ name: 'AEGIS Bio Digital Lab 10 ECONOMIC PROCESSOR', url: `${data.base_url}/economic`, icon: 'dollar-sign', color: 'yellow', description: 'Economic modeling' },
{ name: 'AEGIS BIO DIGITAL LAB 10 WAR ANALYST EXPERT', url: `${data.base_url}/war`, icon: 'shield', color: 'red', description: 'Strategic analysis' },
{ name: 'AEGIS BIO DIGITAL LAB 10 DISEASE ANALYST EXPERT SYSTEM', url: `${data.base_url}/disease`, icon: 'bug', color: 'orange', description: 'Disease tracking' },
{ name: 'AEGIS-BIO LAB-10 VANGUARD', url: `${data.base_url}/deepseek`, icon: 'brain', color: 'indigo', description: 'AI deep analysis' },
{ name: 'AEGIS-BIO LAB-10 3D IMAGE LAB ASSISTANT', url: `${data.base_url}/image`, icon: 'image', color: 'pink', description: 'Image processing' },
{ name: 'AEGIS Bio Digital Lab 10 - Drug Development System', url: `${data.base_url}/drug`, icon: 'pill', color: 'teal', description: 'Pharmaceutical research' },
{ name: 'AEGIS BIO DIGITAL LAB 10 Trail - Visual Kinetic System', url: `${data.base_url}/visual-kinetic`, icon: 'eye', color: 'cyan', description: 'Visual analytics' }
];
}
}
} catch (error) {
console.warn('Failed to load portals from API:', error);
}
// Check if portals data is available from server-side injection
if (!portalsData && window.portalsData && window.portalsData.length > 0) {
portalsData = window.portalsData;
}
// Fallback to default portals if no data available
const portalsToUse = portalsData || defaultPortals;
// Check if data has actually changed to avoid unnecessary updates
const newDataString = JSON.stringify(portalsToUse);
const currentDataString = JSON.stringify(currentPortalsData);
if (!forceUpdate && !isInitialLoad && newDataString === currentDataString) {
// No changes, skip update to prevent flickering
return;
}
// Store current data
currentPortalsData = portalsToUse;
// Smooth update: only update URLs if structure is the same
if (!isInitialLoad && portalsToUse.length === (currentPortalsData?.length || 0)) {
updatePortalURLsSilently(portalsToUse);
} else {
// Full rebuild only when necessary
renderPortalsComplete(portalsToUse);
}
isInitialLoad = false;
}
// Silently update only the URLs without rebuilding the entire DOM
function updatePortalURLsSilently(portalsData) {
const portalCards = document.querySelectorAll('.portal-card');
portalsData.forEach((portal, index) => {
const card = portalCards[index];
if (card) {
// Update onclick handler
card.onclick = () => openPortal(portal.url, portal.name);
// Update launch button
const launchBtn = card.querySelector('.launch-btn');
if (launchBtn) {
launchBtn.href = portal.url;
launchBtn.onclick = (e) => {
e.stopPropagation();
openPortal(portal.url, portal.name);
};
}
}
});
}
// Complete portal rendering (used for initial load or structure changes)
function renderPortalsComplete(portalsData) {
const container = document.getElementById('portals-container');
container.innerHTML = portalsData.map(portal => `
<div class="portal-card" onclick="openPortal('${portal.url}', '${portal.name}')">
<div class="portal-icon color-${portal.color}">
<i data-lucide="${portal.icon}"></i>
</div>
<h3>${portal.name}</h3>
<p>${portal.description}</p>
<a href="${portal.url}" class="launch-btn" onclick="event.stopPropagation(); openPortal('${portal.url}', '${portal.name}')">
<span>Launch Portal</span>
<i data-lucide="external-link"></i>
</a>
</div>
`).join('');
lucide.createIcons();
}
// Open portal
function openPortal(url) {
if (url && url !== '#') {
window.open(url, '_blank', 'noopener,noreferrer');
}
}
// Global state for status updates
let lastStatusData = null;
// Check system status with silent updates
async function checkStatus() {
try {
const response = await fetch('/api/status');
const data = await response.json();
// Check if status data has actually changed
const newStatusString = JSON.stringify(data);
const lastStatusString = JSON.stringify(lastStatusData);
if (newStatusString === lastStatusString) {
// No changes, skip DOM updates to prevent flickering
return;
}
// Store new status data
lastStatusData = data;
const statusIcon = document.getElementById('status-icon');
const statusText = document.getElementById('status-text');
const connectionStatus = document.getElementById('connection-status');
if (data.status === 'online') {
statusIcon.setAttribute('data-lucide', 'wifi');
if (data.base_url && data.base_url !== '') {
statusText.textContent = `Live URLs Active (${data.portals_count}/${data.total_portals})`;
connectionStatus.className = 'status-item status-online';
} else {
statusText.textContent = 'Online - Waiting for URLs';
connectionStatus.className = 'status-item status-online';
}
} else {
statusIcon.setAttribute('data-lucide', 'wifi-off');
statusText.textContent = 'System Offline';
connectionStatus.className = 'status-item status-offline';
}
// Update last updated time if available
if (data.last_updated) {
const lastUpdated = new Date(data.last_updated);
const timeAgo = getTimeAgo(lastUpdated);
const timeElement = document.querySelector('.status-item:last-child span');
if (timeElement) {
timeElement.textContent = `Updated ${timeAgo}`;
}
}
// Only recreate icons if status actually changed
lucide.createIcons();
} catch (error) {
// Only update UI if error state is different from current state
if (!lastStatusData || lastStatusData.status !== 'error') {
const statusIcon = document.getElementById('status-icon');
const statusText = document.getElementById('status-text');
const connectionStatus = document.getElementById('connection-status');
statusIcon.setAttribute('data-lucide', 'wifi-off');
statusText.textContent = 'Connection Error';
connectionStatus.className = 'status-item status-offline';
lastStatusData = { status: 'error' };
lucide.createIcons();
}
}
}
// Helper function to get time ago
function getTimeAgo(date) {
const now = new Date();
const diffMs = now - date;
const diffSecs = Math.floor(diffMs / 1000);
const diffMins = Math.floor(diffSecs / 60);
const diffHours = Math.floor(diffMins / 60);
if (diffSecs < 60) return `${diffSecs}s ago`;
if (diffMins < 60) return `${diffMins}m ago`;
if (diffHours < 24) return `${diffHours}h ago`;
return date.toLocaleDateString();
}
// Update portal URLs function with silent update indicator
async function updatePortalUrls(newUrls) {
try {
// Add subtle update indicator without disrupting layout
const statusBar = document.querySelector('.status-bar');
const originalOpacity = statusBar.style.opacity;
statusBar.style.opacity = '0.7';
const response = await fetch('/api/update-urls', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ urls: newUrls })
});
const data = await response.json();
// Restore opacity
setTimeout(() => {
statusBar.style.opacity = originalOpacity || '1';
}, 300);
if (data.success) {
// Use debounced update to prevent flickering
debouncedUpdate(() => {
loadPortals();
}, 100);
return true;
} else {
console.error('Failed to update URLs:', data.error);
return false;
}
} catch (error) {
console.error('Error updating URLs:', error);
return false;
}
}
// Initialize dashboard with optimized updates
function initDashboard() {
updateTime();
loadPortals(true); // Force initial load
checkStatus();
// Update time every second
setInterval(updateTime, 1000);
// Check status and reload portals every 10 seconds with silent updates
setInterval(() => {
checkStatus();
loadPortals(); // Silent update - only updates if data changed
}, 10000);
}
// Handle keyboard shortcuts
document.addEventListener('keydown', function(event) {
// Ctrl/Cmd + R to refresh portals
if ((event.ctrlKey || event.metaKey) && event.key === 'r') {
event.preventDefault();
loadPortals();
checkStatus();
}
});
// Handle window focus/blur for status updates with debouncing
window.addEventListener('focus', function() {
debouncedUpdate(() => {
checkStatus();
loadPortals();
}, 200);
});
// Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', initDashboard);
// Handle portal card animations
document.addEventListener('mouseover', function(event) {
if (event.target.closest('.portal-card')) {
event.target.closest('.portal-card').style.transform = 'translateY(-5px) scale(1.02)';
}
});
document.addEventListener('mouseout', function(event) {
if (event.target.closest('.portal-card')) {
event.target.closest('.portal-card').style.transform = 'translateY(0) scale(1)';
}
});
// Add loading states
function showLoading(elementId) {
const element = document.getElementById(elementId);
if (element) {
element.innerHTML = '<i data-lucide="loader-2" class="animate-spin"></i> Loading...';
lucide.createIcons();
}
}
function hideLoading(elementId, originalContent) {
const element = document.getElementById(elementId);
if (element) {
element.innerHTML = originalContent;
lucide.createIcons();
}
}
// Add error handling for portal launches
function handlePortalError(portalName, error) {
console.error(`Error launching ${portalName}:`, error);
// Show user-friendly error message
const errorDiv = document.createElement('div');
errorDiv.className = 'error-notification';
errorDiv.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
background: rgba(244, 67, 54, 0.9);
color: white;
padding: 1rem;
border-radius: 8px;
z-index: 1000;
max-width: 300px;
backdrop-filter: blur(10px);
`;
errorDiv.innerHTML = `
<div style="display: flex; align-items: center; gap: 0.5rem;">
<i data-lucide="alert-circle"></i>
<span>Failed to launch ${portalName}</span>
</div>
`;
document.body.appendChild(errorDiv);
lucide.createIcons();
// Remove error message after 5 seconds
setTimeout(() => {
if (errorDiv.parentNode) {
errorDiv.parentNode.removeChild(errorDiv);
}
}, 5000);
}
// Enhanced portal opening with error handling
function openPortalSafe(url, name) {
try {
if (url && url !== '#') {
const newWindow = window.open(url, '_blank', 'noopener,noreferrer');
// Check if popup was blocked
if (!newWindow || newWindow.closed || typeof newWindow.closed === 'undefined') {
throw new Error('Popup blocked or failed to open');
}
} else {
throw new Error('Invalid URL');
}
} catch (error) {
handlePortalError(name, error);
}
}
// Update the openPortal function to use the safer version
function openPortal(url, name = 'Portal') {
openPortalSafe(url, name);
}
// Add service worker registration for offline support
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
console.log('ServiceWorker registration successful');
})
.catch(function(error) {
console.log('ServiceWorker registration failed');
});
});
}
</script>
</body>
</html>