argobox-portfolio/src/pages/dashboard.astro

448 lines
18 KiB
Plaintext

---
// src/pages/dashboard.astro - Converted from static dashboard.html
import BaseLayout from '../layouts/BaseLayout.astro';
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';
const title = "ArgoBox Dashboard | Status and Metrics";
const description = "ArgoBox operations dashboard provides real-time monitoring and status updates for all lab services and infrastructure components.";
// Placeholder data - In a real app, this would come from an API or state management
const systemHealth = { cpu: 23, memory: 42, disk: 78, temp: 52, uptime: "14d 7h 32m" };
const network = { throughput: 12.4, latency: 4, connections: 127, packetLoss: 0.02, dns: 2 };
const kubernetes = { nodes: "3/3 Ready", pods: "42/44 Running", deployments: "11/12 Healthy", memoryPressure: "Low", load: 37 };
const storage = { nasStatus: "Online", raidHealth: "Optimal", iops: 243, diskIO: 2.4, backups: "Completed 07/24" };
const serviceStatus = [
{ name: "Kubernetes API", status: "online" },
{ name: "Container Registry", status: "online" },
{ name: "Monitoring", status: "degraded" },
{ name: "Authentication", status: "online" },
{ name: "Database", status: "online" },
{ name: "Storage", status: "online" },
{ name: "CI/CD", status: "offline" },
{ name: "Logging", status: "online" },
];
const weeklyTraffic = [65, 80, 45, 90, 60, 75, 40]; // Example percentages
const recentAlerts = [
{ name: "CI/CD Pipeline Failure", level: "error", time: "3h ago" },
{ name: "High Memory Usage", level: "warning", time: "6h ago" },
{ name: "Monitoring Service Degraded", level: "warning", time: "12h ago" },
{ name: "Backup Completed", level: "healthy", time: "1d ago" },
{ name: "Security Update Available", level: "warning", time: "2d ago" },
];
// Function to determine status class
const getStatusClass = (value: number | string, type: 'percent' | 'temp' | 'loss' | 'latency' | 'dns' | 'status') => {
if (type === 'status') {
return value === 'Online' || value === 'Optimal' || value === 'Low' || value === 'Ready' || value === 'Running' || value === 'Completed' ? 'healthy' :
value === 'Degraded' || value === 'Warning' ? 'warning' : 'error';
}
if (typeof value !== 'number') return '';
if (type === 'percent') return value > 90 ? 'error' : value > 75 ? 'warning' : 'healthy';
if (type === 'temp') return value > 70 ? 'error' : value > 60 ? 'warning' : 'healthy';
if (type === 'loss') return value > 1 ? 'error' : value > 0.1 ? 'warning' : 'healthy';
if (type === 'latency' || type === 'dns') return value > 50 ? 'error' : value > 10 ? 'warning' : 'healthy';
return 'healthy';
};
---
<BaseLayout {title} {description}>
{/* Add Font Awesome if not loaded globally */}
{/* <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer" /> */}
<Header slot="header" />
<main class="dashboard-container">
<div class="container">
<!-- Offline Notice Banner -->
<div class="offline-notice" id="offline-notice">
<div class="offline-notice-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<div class="offline-notice-text">
<h3>Dashboard is Currently Offline</h3>
<p>The live dashboard is currently in simulation mode using placeholder data. Real-time data is not available.</p>
</div>
</div>
<header class="dashboard-header">
<h1 class="dashboard-title">Infrastructure Status Dashboard</h1>
<p class="dashboard-subtitle">Monitoring and metrics for ArgoBox infrastructure components</p>
</header>
<div class="dashboard-grid">
<!-- System Health Card -->
<div class="dashboard-card">
<div class="card-header">
<h2 class="card-title">System Health</h2>
<div class="card-icon"><i class="fas fa-heartbeat"></i></div>
</div>
<div class="metric-list">
<div class="metric-item">
<span class="metric-name">CPU Usage</span>
<span class={`metric-value ${getStatusClass(systemHealth.cpu, 'percent')}`}>{systemHealth.cpu}%</span>
</div>
<div class="metric-item">
<span class="metric-name">Memory Usage</span>
<span class={`metric-value ${getStatusClass(systemHealth.memory, 'percent')}`}>{systemHealth.memory}%</span>
</div>
<div class="metric-item">
<span class="metric-name">Disk Space</span>
<span class={`metric-value ${getStatusClass(systemHealth.disk, 'percent')}`}>{systemHealth.disk}%</span>
</div>
<div class="metric-item">
<span class="metric-name">Temperature</span>
<span class={`metric-value ${getStatusClass(systemHealth.temp, 'temp')}`}>{systemHealth.temp}°C</span>
</div>
<div class="metric-item">
<span class="metric-name">Uptime</span>
<span class="metric-value">{systemHealth.uptime}</span>
</div>
</div>
</div>
<!-- Network Card -->
<div class="dashboard-card">
<div class="card-header">
<h2 class="card-title">Network</h2>
<div class="card-icon"><i class="fas fa-network-wired"></i></div>
</div>
<div class="metric-list">
<div class="metric-item">
<span class="metric-name">Throughput</span>
<span class="metric-value healthy">{network.throughput} MB/s</span>
</div>
<div class="metric-item">
<span class="metric-name">Latency</span>
<span class={`metric-value ${getStatusClass(network.latency, 'latency')}`}>{network.latency}ms</span>
</div>
<div class="metric-item">
<span class="metric-name">Active Connections</span>
<span class="metric-value">{network.connections}</span>
</div>
<div class="metric-item">
<span class="metric-name">Packet Loss</span>
<span class={`metric-value ${getStatusClass(network.packetLoss, 'loss')}`}>{network.packetLoss}%</span>
</div>
<div class="metric-item">
<span class="metric-name">DNS Response</span>
<span class={`metric-value ${getStatusClass(network.dns, 'dns')}`}>{network.dns}ms</span>
</div>
</div>
</div>
<!-- Kubernetes Card -->
<div class="dashboard-card">
<div class="card-header">
<h2 class="card-title">Kubernetes</h2>
<div class="card-icon"><i class="fas fa-dharmachakra"></i></div>
</div>
<div class="metric-list">
<div class="metric-item">
<span class="metric-name">Node Status</span>
<span class={`metric-value ${kubernetes.nodes.includes('Ready') && !kubernetes.nodes.includes('/') ? 'healthy' : 'warning'}`}>{kubernetes.nodes}</span>
</div>
<div class="metric-item">
<span class="metric-name">Pods</span>
<span class={`metric-value ${kubernetes.pods.includes('Running') && !kubernetes.pods.includes('/') ? 'healthy' : 'warning'}`}>{kubernetes.pods}</span>
</div>
<div class="metric-item">
<span class="metric-name">Deployments</span>
<span class={`metric-value ${kubernetes.deployments.includes('Healthy') && !kubernetes.deployments.includes('/') ? 'healthy' : 'warning'}`}>{kubernetes.deployments}</span>
</div>
<div class="metric-item">
<span class="metric-name">Memory Pressure</span>
<span class={`metric-value ${getStatusClass(kubernetes.memoryPressure, 'status')}`}>{kubernetes.memoryPressure}</span>
</div>
<div class="metric-item">
<span class="metric-name">Cluster Load</span>
<span class={`metric-value ${getStatusClass(kubernetes.load, 'percent')}`}>{kubernetes.load}%</span>
</div>
</div>
</div>
<!-- Storage Card -->
<div class="dashboard-card">
<div class="card-header">
<h2 class="card-title">Storage</h2>
<div class="card-icon"><i class="fas fa-hdd"></i></div>
</div>
<div class="metric-list">
<div class="metric-item">
<span class="metric-name">NAS Status</span>
<span class={`metric-value ${getStatusClass(storage.nasStatus, 'status')}`}>{storage.nasStatus}</span>
</div>
<div class="metric-item">
<span class="metric-name">RAID Health</span>
<span class={`metric-value ${getStatusClass(storage.raidHealth, 'status')}`}>{storage.raidHealth}</span>
</div>
<div class="metric-item">
<span class="metric-name">IOPS</span>
<span class="metric-value">{storage.iops}</span>
</div>
<div class="metric-item">
<span class="metric-name">Disk I/O</span>
<span class="metric-value healthy">{storage.diskIO} MB/s</span>
</div>
<div class="metric-item">
<span class="metric-name">Backups</span>
<span class={`metric-value ${getStatusClass(storage.backups, 'status')}`}>{storage.backups}</span>
</div>
</div>
</div>
</div>
<!-- Service Status Card -->
<div class="dashboard-card">
<div class="card-header">
<h2 class="card-title">Service Status</h2>
<div class="card-icon"><i class="fas fa-server"></i></div>
</div>
<div class="status-grid">
{serviceStatus.map(service => (
<div class="status-item">
<div class={`status-indicator ${service.status}`}></div>
<span class="status-name">{service.name}</span>
</div>
))}
</div>
</div>
<!-- Grid for Charts/Alerts -->
<div class="dashboard-grid">
<!-- Weekly Traffic Card -->
<div class="dashboard-card">
<div class="card-header">
<h2 class="card-title">Weekly Traffic</h2>
<div class="card-icon"><i class="fas fa-chart-line"></i></div>
</div>
<div class="chart-container">
{weeklyTraffic.map(value => (
<div class="chart-bar" style={`height: ${value}%;`}></div>
))}
</div>
<div class="chart-labels">
<div class="chart-label">Mon</div>
<div class="chart-label">Tue</div>
<div class="chart-label">Wed</div>
<div class="chart-label">Thu</div>
<div class="chart-label">Fri</div>
<div class="chart-label">Sat</div>
<div class="chart-label">Sun</div>
</div>
</div>
<!-- Recent Alerts Card -->
<div class="dashboard-card">
<div class="card-header">
<h2 class="card-title">Recent Alerts</h2>
<div class="card-icon"><i class="fas fa-bell"></i></div>
</div>
<div class="metric-list">
{recentAlerts.map(alert => (
<div class="metric-item">
<span class={`metric-name alert-${alert.level}`}>{alert.name}</span>
<span class={`metric-value ${alert.level}`}>{alert.time}</span>
</div>
))}
</div>
</div>
</div>
</div>
</main>
<Footer slot="footer" />
</BaseLayout>
<style is:global>
/* Dashboard-specific styles adapted from dashboard.html */
/* Use theme variables */
.dashboard-container {
padding-top: 2rem; /* Add space from header */
padding-bottom: 4rem;
}
.dashboard-header {
margin-bottom: 2rem;
text-align: center; /* Center header */
}
.dashboard-title {
font-size: clamp(1.8rem, 5vw, 2.5rem); /* Responsive title */
font-weight: 700;
margin-bottom: 0.5rem;
color: var(--text-primary);
}
.dashboard-subtitle {
color: var(--text-secondary);
font-size: clamp(1rem, 3vw, 1.1rem);
}
.dashboard-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
margin-bottom: 2rem;
}
.dashboard-card {
background-color: var(--card-bg);
border: 1px solid var(--border);
border-radius: 1rem;
padding: 1.5rem;
transition: all var(--transition-normal);
}
.dashboard-card:hover {
background-color: var(--card-hover-bg);
transform: translateY(-5px);
box-shadow: var(--card-shadow);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 1rem;
padding-bottom: 0.75rem; /* Add padding */
border-bottom: 1px solid var(--border); /* Add border */
}
.card-title {
font-weight: 600;
font-size: 1.1rem;
color: var(--text-primary);
}
.card-icon {
color: var(--accent);
font-size: 1.25rem;
}
.metric-list { display: grid; gap: 1rem; }
.metric-item { display: flex; justify-content: space-between; align-items: center; font-size: 0.9rem; }
.metric-name { color: var(--text-secondary); }
.metric-value { font-family: var(--font-mono); font-weight: 500; color: var(--text-primary); }
.metric-value.healthy { color: var(--success); }
.metric-value.warning { color: var(--warning); }
.metric-value.error { color: var(--error); }
.metric-name.alert-warning { color: var(--warning); font-weight: 500; } /* Style alert names */
.metric-name.alert-error { color: var(--error); font-weight: 500; }
.metric-name.alert-healthy { color: var(--text-secondary); } /* Normal color for healthy alerts */
.status-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); /* Adjust minmax */
gap: 1rem;
}
.status-item {
display: flex;
align-items: center;
gap: 0.75rem; /* Increased gap */
background-color: var(--bg-secondary); /* Use secondary bg */
padding: 0.75rem;
border-radius: 0.5rem;
border: 1px solid var(--border);
}
.status-indicator {
width: 10px; /* Slightly smaller */
height: 10px;
border-radius: 50%;
flex-shrink: 0; /* Prevent shrinking */
}
.status-indicator.online { background-color: var(--success); box-shadow: 0 0 8px rgba(16, 185, 129, 0.4); } /* Adjusted shadow */
.status-indicator.offline { background-color: var(--error); box-shadow: 0 0 8px rgba(239, 68, 68, 0.4); }
.status-indicator.degraded { background-color: var(--warning); box-shadow: 0 0 8px rgba(245, 158, 11, 0.4); }
.status-name { font-size: 0.9rem; color: var(--text-primary); }
.chart-container {
height: 180px; /* Slightly smaller */
margin-top: 1rem;
display: flex;
align-items: flex-end;
gap: 0.5rem;
padding: 0.5rem; /* Add padding */
background-color: var(--bg-secondary); /* Add background */
border-radius: 0.5rem; /* Add radius */
border: 1px solid var(--border);
}
.chart-bar {
flex: 1;
background-color: rgba(59, 130, 246, 0.2);
border-radius: 4px 4px 0 0;
position: relative;
min-height: 5px; /* Ensure visibility */
transition: height 0.5s ease; /* Animate height */
}
/* Removed ::before for simplicity, height set directly */
.chart-labels { display: flex; justify-content: space-between; margin-top: 0.5rem; }
.chart-label { font-size: 0.75rem; color: var(--text-secondary); flex: 1; text-align: center; }
.offline-notice {
background-color: rgba(239, 68, 68, 0.1); /* Use theme error */
border: 1px solid rgba(239, 68, 68, 0.3);
border-left: 4px solid var(--error); /* Use theme error */
border-radius: 0.5rem;
padding: 1.5rem;
margin-bottom: 2rem;
display: flex;
align-items: center;
gap: 1.5rem;
}
.offline-notice-icon { color: var(--error); font-size: 2rem; flex-shrink: 0; }
.offline-notice-text h3 { font-weight: 600; margin-bottom: 0.5rem; color: var(--error); }
.offline-notice-text p { color: var(--text-secondary); margin: 0; }
@media (max-width: 768px) {
.dashboard-grid { grid-template-columns: 1fr; }
.status-grid { grid-template-columns: 1fr 1fr; }
.offline-notice { flex-direction: column; text-align: center; gap: 1rem; }
}
</style>
<script is:inline>
// Placeholder for potential future JS to fetch real data
// For now, it uses the static data from the frontmatter
// Example: Function to update status indicators dynamically
// function updateServiceStatus(serviceName, status) {
// const item = document.querySelector(`.service-${serviceName.toLowerCase().replace(/\s+/g, '-')}`); // Requires adding class to status items
// if (item) {
// const indicator = item.querySelector('.status-indicator');
// indicator.className = `status-indicator ${status}`; // status should be 'online', 'offline', 'degraded'
// }
// }
// Example: Function to update metrics
// function updateMetric(metricNameSelector, value, statusClass = '') {
// const valueEl = document.querySelector(metricNameSelector); // Need unique selectors for each metric value
// if (valueEl) {
// valueEl.textContent = value;
// valueEl.className = `metric-value ${statusClass}`;
// }
// }
// Example: Fetch data and update UI
// async function fetchDashboardData() {
// try {
// const response = await fetch('/api/dashboard-status'); // Your API endpoint
// if (!response.ok) throw new Error('Failed to fetch status');
// const data = await response.json();
//
// // Update UI elements based on data
// // updateMetric('#cpu-usage', data.systemHealth.cpu, getStatusClass(data.systemHealth.cpu, 'percent'));
// // ... update other metrics ...
// // data.serviceStatus.forEach(service => updateServiceStatus(service.name, service.status));
//
// } catch (error) {
// console.error("Error fetching dashboard data:", error);
// // Show error state in UI
// }
// }
// document.addEventListener('DOMContentLoaded', () => {
// // fetchDashboardData(); // Initial fetch
// // setInterval(fetchDashboardData, 60000); // Fetch every minute
// });
</script>