448 lines
18 KiB
Plaintext
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> |