From 563a800fad5f62356df07b5efdb65a0173551cbe Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 20 Oct 2025 21:06:04 +0000 Subject: [PATCH] Add comprehensive Storage Device Performance Dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces a complete web-based dashboard for monitoring storage device performance metrics in real-time. Features: - Real-time monitoring with auto-refresh every 5 seconds - Comprehensive metrics collection (disk usage, I/O stats, IOPS, SMART data) - Interactive visualizations using Chart.js - Modern dark-themed responsive UI - Python Flask backend with REST API - System information and uptime tracking - Historical performance trend charts Tech Stack: - Backend: Python 3.8+, Flask, psutil, pySMART - Frontend: HTML5, CSS3, JavaScript ES6+, Chart.js - Cross-platform support with startup scripts for Linux/Windows The dashboard provides system administrators and monitoring enthusiasts with a powerful tool to track storage performance, identify bottlenecks, and monitor disk health. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- Storage Dashboard/.gitignore | 47 ++ Storage Dashboard/README.md | 334 +++++++++++++ Storage Dashboard/backend/app.py | 70 +++ .../backend/metrics_collector.py | 149 ++++++ Storage Dashboard/backend/requirements.txt | 4 + Storage Dashboard/frontend/dashboard.js | 446 ++++++++++++++++++ Storage Dashboard/frontend/index.html | 121 +++++ Storage Dashboard/frontend/styles.css | 386 +++++++++++++++ Storage Dashboard/start.bat | 45 ++ Storage Dashboard/start.sh | 42 ++ 10 files changed, 1644 insertions(+) create mode 100644 Storage Dashboard/.gitignore create mode 100644 Storage Dashboard/README.md create mode 100644 Storage Dashboard/backend/app.py create mode 100644 Storage Dashboard/backend/metrics_collector.py create mode 100644 Storage Dashboard/backend/requirements.txt create mode 100644 Storage Dashboard/frontend/dashboard.js create mode 100644 Storage Dashboard/frontend/index.html create mode 100644 Storage Dashboard/frontend/styles.css create mode 100644 Storage Dashboard/start.bat create mode 100755 Storage Dashboard/start.sh diff --git a/Storage Dashboard/.gitignore b/Storage Dashboard/.gitignore new file mode 100644 index 00000000..578c57ca --- /dev/null +++ b/Storage Dashboard/.gitignore @@ -0,0 +1,47 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# Virtual Environment +venv/ +ENV/ +env/ +.venv + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS +.DS_Store +Thumbs.db + +# Logs +*.log + +# Environment variables +.env diff --git a/Storage Dashboard/README.md b/Storage Dashboard/README.md new file mode 100644 index 00000000..a4e2272c --- /dev/null +++ b/Storage Dashboard/README.md @@ -0,0 +1,334 @@ +# Storage Device Performance Dashboard + +A comprehensive, real-time web-based dashboard for monitoring storage device performance metrics. Track disk usage, I/O performance, IOPS, SMART health data, and more with beautiful visualizations. + +![Dashboard Preview](https://img.shields.io/badge/Status-Active-success) +![Python](https://img.shields.io/badge/Python-3.8+-blue) +![Flask](https://img.shields.io/badge/Flask-3.0.0-green) + +## Features + +### Real-time Monitoring +- **Auto-refresh every 5 seconds** - Stay updated with the latest metrics +- **Live performance charts** - Visualize trends over time +- **Instant metrics** - See current performance at a glance + +### Comprehensive Metrics + +#### 1. System Information +- Platform and OS details +- System hostname +- Uptime statistics +- Boot time + +#### 2. Disk Partitions & Usage +- All mounted partitions +- Total, used, and free space +- Usage percentage with visual progress bars +- File system type +- Mount points + +#### 3. I/O Performance +- Read/Write speeds (MB/s) +- IOPS (Input/Output Operations Per Second) +- Total data read/written +- Real-time speed calculations +- Historical trend charts + +#### 4. SMART Health Data +- Disk health status +- Temperature monitoring +- Power-on hours +- Power cycle count +- Model and serial information +- *Note: Requires root/admin privileges* + +#### 5. Visual Analytics +- Interactive bar charts for current performance +- Line charts for historical trends +- Color-coded health indicators +- Responsive data tables + +## Technology Stack + +### Backend +- **Python 3.8+** - Core language +- **Flask 3.0.0** - Web framework +- **psutil 5.9.6** - System metrics collection +- **pySMART 1.3.0** - SMART data retrieval +- **Flask-CORS 4.0.0** - Cross-origin resource sharing + +### Frontend +- **HTML5/CSS3** - Structure and styling +- **JavaScript (ES6+)** - Interactive functionality +- **Chart.js 4.4.0** - Data visualization +- **Modern responsive design** - Works on all devices + +## Installation + +### Prerequisites +- Python 3.8 or higher +- pip (Python package manager) +- Modern web browser (Chrome, Firefox, Safari, Edge) + +### Step 1: Clone or Navigate to the Directory +```bash +cd "Storage Dashboard" +``` + +### Step 2: Install Python Dependencies +```bash +cd backend +pip install -r requirements.txt +``` + +### Step 3: Start the Backend Server +```bash +python app.py +``` + +The server will start on `http://localhost:5000` + +### Step 4: Access the Dashboard +Open your web browser and navigate to: +``` +http://localhost:5000 +``` + +## Usage + +### Dashboard Interface + +1. **System Information Panel** + - View OS details and system uptime + - Located at the top of the dashboard + +2. **Disk Partitions Section** + - Each partition displayed as a card + - Color-coded progress bars: + - Blue: < 75% usage (healthy) + - Orange: 75-90% usage (warning) + - Red: > 90% usage (critical) + +3. **I/O Performance Charts** + - Bar charts show current read/write speeds and IOPS + - View all disks simultaneously + +4. **Detailed Statistics Table** + - Comprehensive I/O metrics in tabular format + - Sortable and easy to read + +5. **SMART Health Section** + - Individual cards for each storage device + - Health status badges (Good/Warning/Bad) + - Detailed device information + +6. **Historical Performance** + - Line charts track performance over time + - Shows trends for the last 20 data points + - Auto-updates every 5 seconds + +### Manual Refresh +Click the "Refresh Now" button in the header to manually update all metrics. + +## API Endpoints + +The backend provides the following REST API endpoints: + +| Endpoint | Method | Description | +|----------|--------|-------------| +| `/api/metrics` | GET | Get all metrics in one call | +| `/api/partitions` | GET | Get disk partition information | +| `/api/io-stats` | GET | Get I/O statistics | +| `/api/smart` | GET | Get SMART health data | +| `/api/system-info` | GET | Get system information | + +### Example API Response + +```json +{ + "timestamp": "2024-01-15T10:30:00", + "system_info": { + "platform": "Linux", + "hostname": "server-01", + "uptime_days": 15, + "uptime_hours": 6, + "uptime_minutes": 23 + }, + "partitions": [ + { + "device": "/dev/sda1", + "mountpoint": "/", + "fstype": "ext4", + "total_gb": 500.0, + "used_gb": 250.0, + "free_gb": 250.0, + "percent": 50.0 + } + ], + "io_stats": [ + { + "disk": "sda", + "read_speed_mbps": 45.2, + "write_speed_mbps": 23.1, + "iops_read": 1200, + "iops_write": 800 + } + ] +} +``` + +## Configuration + +### Change Refresh Interval +Edit `frontend/dashboard.js`: +```javascript +const REFRESH_INTERVAL = 5000; // milliseconds (5 seconds) +``` + +### Change Historical Data Length +Edit `frontend/dashboard.js`: +```javascript +const HISTORY_LENGTH = 20; // number of data points +``` + +### Change Server Port +Edit `backend/app.py`: +```python +app.run(debug=True, host='0.0.0.0', port=5000) +``` + +## SMART Data Collection + +SMART data provides valuable insights into disk health but requires elevated privileges: + +### Linux +```bash +sudo python app.py +``` + +### Windows +Run Command Prompt or PowerShell as Administrator: +```bash +python app.py +``` + +### macOS +```bash +sudo python app.py +``` + +If SMART data is unavailable, the dashboard will display a message indicating that root/admin privileges are required. + +## Troubleshooting + +### Backend Won't Start +- Ensure Python 3.8+ is installed: `python --version` +- Verify all dependencies are installed: `pip install -r requirements.txt` +- Check if port 5000 is available + +### No Data Displayed +- Ensure the backend server is running +- Check browser console for errors (F12) +- Verify the API URL in `dashboard.js` matches your backend + +### SMART Data Not Available +- SMART data requires root/admin privileges +- Run the backend with elevated permissions +- Some virtual machines may not expose SMART data + +### Charts Not Rendering +- Ensure you have internet connectivity (Chart.js loads from CDN) +- Check browser console for JavaScript errors +- Try clearing browser cache + +## Security Considerations + +- The dashboard runs on `0.0.0.0` (all interfaces) by default +- For production use, configure proper firewall rules +- Consider using HTTPS for secure connections +- Implement authentication for sensitive environments + +## Development + +### Project Structure +``` +Storage Dashboard/ +├── backend/ +│ ├── app.py # Flask application +│ ├── metrics_collector.py # Metrics collection logic +│ └── requirements.txt # Python dependencies +└── frontend/ + ├── index.html # Main dashboard page + ├── styles.css # Styling + └── dashboard.js # Frontend logic and charts +``` + +### Extending the Dashboard + +#### Add New Metrics +1. Add collection method in `metrics_collector.py` +2. Create API endpoint in `app.py` +3. Update frontend to display the new data + +#### Customize Appearance +- Modify `styles.css` to change colors and layout +- Update CSS variables in `:root` for theme changes + +## Performance + +- **Low overhead** - Minimal impact on system performance +- **Efficient polling** - Only collects data when needed +- **Optimized charts** - Smooth animations and updates +- **Responsive design** - Fast load times + +## Browser Compatibility + +- Chrome/Edge 90+ +- Firefox 88+ +- Safari 14+ +- Opera 76+ + +## License + +This project is open source and available for personal and commercial use. + +## Contributing + +Contributions are welcome! Feel free to: +- Report bugs +- Suggest new features +- Submit pull requests +- Improve documentation + +## Support + +For issues or questions: +1. Check the Troubleshooting section +2. Review the API documentation +3. Examine browser console logs +4. Check backend server logs + +## Future Enhancements + +Planned features: +- [ ] Export metrics to CSV/JSON +- [ ] Email/SMS alerts for critical conditions +- [ ] Multi-server monitoring +- [ ] Historical data persistence +- [ ] Custom metric thresholds +- [ ] Network storage support +- [ ] Docker containerization + +## Screenshots + +The dashboard features: +- Dark theme optimized for monitoring +- Color-coded status indicators +- Real-time updating charts +- Responsive grid layouts +- Professional UI design + +--- + +**Built with ❤️ for system administrators and monitoring enthusiasts** diff --git a/Storage Dashboard/backend/app.py b/Storage Dashboard/backend/app.py new file mode 100644 index 00000000..c5feb5d0 --- /dev/null +++ b/Storage Dashboard/backend/app.py @@ -0,0 +1,70 @@ +from flask import Flask, jsonify, send_from_directory +from flask_cors import CORS +from metrics_collector import StorageMetricsCollector +import os + +app = Flask(__name__, static_folder='../frontend') +CORS(app) + +# Initialize metrics collector +collector = StorageMetricsCollector() + +@app.route('/') +def index(): + """Serve the main dashboard page""" + return send_from_directory('../frontend', 'index.html') + +@app.route('/api/metrics') +def get_metrics(): + """Get all storage metrics""" + try: + metrics = collector.get_all_metrics() + return jsonify(metrics) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/partitions') +def get_partitions(): + """Get disk partition information""" + try: + partitions = collector.get_disk_partitions() + return jsonify(partitions) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/io-stats') +def get_io_stats(): + """Get I/O statistics""" + try: + io_stats = collector.get_io_stats() + return jsonify(io_stats) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/smart') +def get_smart(): + """Get SMART data for disks""" + try: + smart_data = collector.get_smart_data() + return jsonify(smart_data) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/api/system-info') +def get_system_info(): + """Get system information""" + try: + system_info = collector.get_system_info() + return jsonify(system_info) + except Exception as e: + return jsonify({'error': str(e)}), 500 + +@app.route('/') +def serve_static(path): + """Serve static files""" + return send_from_directory('../frontend', path) + +if __name__ == '__main__': + print("Starting Storage Dashboard Server...") + print("Access the dashboard at: http://localhost:5000") + app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/Storage Dashboard/backend/metrics_collector.py b/Storage Dashboard/backend/metrics_collector.py new file mode 100644 index 00000000..55e43703 --- /dev/null +++ b/Storage Dashboard/backend/metrics_collector.py @@ -0,0 +1,149 @@ +import psutil +import time +import platform +from datetime import datetime + +class StorageMetricsCollector: + """Collects comprehensive storage device metrics""" + + def __init__(self): + self.previous_io = {} + self.last_update = time.time() + + def get_disk_partitions(self): + """Get all disk partitions with their details""" + partitions = [] + for partition in psutil.disk_partitions(all=False): + try: + usage = psutil.disk_usage(partition.mountpoint) + partitions.append({ + 'device': partition.device, + 'mountpoint': partition.mountpoint, + 'fstype': partition.fstype, + 'total': usage.total, + 'used': usage.used, + 'free': usage.free, + 'percent': usage.percent, + 'total_gb': round(usage.total / (1024**3), 2), + 'used_gb': round(usage.used / (1024**3), 2), + 'free_gb': round(usage.free / (1024**3), 2) + }) + except PermissionError: + continue + return partitions + + def get_io_stats(self): + """Get I/O statistics for all disks""" + current_time = time.time() + time_delta = current_time - self.last_update + + io_counters = psutil.disk_io_counters(perdisk=True) + stats = [] + + for disk_name, counter in io_counters.items(): + disk_stats = { + 'disk': disk_name, + 'read_count': counter.read_count, + 'write_count': counter.write_count, + 'read_bytes': counter.read_bytes, + 'write_bytes': counter.write_bytes, + 'read_mb': round(counter.read_bytes / (1024**2), 2), + 'write_mb': round(counter.write_bytes / (1024**2), 2), + 'read_time': counter.read_time, + 'write_time': counter.write_time, + } + + # Calculate rates if we have previous data + if disk_name in self.previous_io and time_delta > 0: + prev = self.previous_io[disk_name] + disk_stats['read_speed_mbps'] = round( + (counter.read_bytes - prev['read_bytes']) / (1024**2) / time_delta, 2 + ) + disk_stats['write_speed_mbps'] = round( + (counter.write_bytes - prev['write_bytes']) / (1024**2) / time_delta, 2 + ) + disk_stats['iops_read'] = round( + (counter.read_count - prev['read_count']) / time_delta, 2 + ) + disk_stats['iops_write'] = round( + (counter.write_count - prev['write_count']) / time_delta, 2 + ) + else: + disk_stats['read_speed_mbps'] = 0 + disk_stats['write_speed_mbps'] = 0 + disk_stats['iops_read'] = 0 + disk_stats['iops_write'] = 0 + + self.previous_io[disk_name] = { + 'read_bytes': counter.read_bytes, + 'write_bytes': counter.write_bytes, + 'read_count': counter.read_count, + 'write_count': counter.write_count + } + + stats.append(disk_stats) + + self.last_update = current_time + return stats + + def get_smart_data(self): + """Get SMART data for disks (requires root/admin privileges)""" + smart_data = [] + try: + from pySMART import DeviceList + devlist = DeviceList() + for device in devlist.devices: + if device: + smart_info = { + 'name': device.name, + 'model': device.model, + 'serial': device.serial, + 'capacity': device.capacity, + 'temperature': device.temperature, + 'health': device.assessment, + 'power_on_hours': None, + 'power_cycle_count': None + } + + # Extract specific SMART attributes + if device.attributes: + for attr in device.attributes: + if attr and hasattr(attr, 'name'): + if 'Power_On_Hours' in attr.name: + smart_info['power_on_hours'] = attr.raw + elif 'Power_Cycle_Count' in attr.name: + smart_info['power_cycle_count'] = attr.raw + + smart_data.append(smart_info) + except Exception as e: + # SMART data requires elevated privileges, return empty if not available + return {'error': str(e), 'message': 'SMART data requires root/admin privileges'} + + return smart_data + + def get_system_info(self): + """Get general system information""" + boot_time = datetime.fromtimestamp(psutil.boot_time()) + uptime = datetime.now() - boot_time + + return { + 'platform': platform.system(), + 'platform_release': platform.release(), + 'platform_version': platform.version(), + 'architecture': platform.machine(), + 'hostname': platform.node(), + 'boot_time': boot_time.strftime("%Y-%m-%d %H:%M:%S"), + 'uptime_days': uptime.days, + 'uptime_hours': uptime.seconds // 3600, + 'uptime_minutes': (uptime.seconds % 3600) // 60 + } + + def get_all_metrics(self): + """Get all storage metrics in one call""" + return { + 'timestamp': datetime.now().isoformat(), + 'system_info': self.get_system_info(), + 'partitions': self.get_disk_partitions(), + 'io_stats': self.get_io_stats(), + 'smart_data': self.get_smart_data() + } diff --git a/Storage Dashboard/backend/requirements.txt b/Storage Dashboard/backend/requirements.txt new file mode 100644 index 00000000..b8086978 --- /dev/null +++ b/Storage Dashboard/backend/requirements.txt @@ -0,0 +1,4 @@ +flask==3.0.0 +flask-cors==4.0.0 +psutil==5.9.6 +pySMART==1.3.0 diff --git a/Storage Dashboard/frontend/dashboard.js b/Storage Dashboard/frontend/dashboard.js new file mode 100644 index 00000000..7b66e777 --- /dev/null +++ b/Storage Dashboard/frontend/dashboard.js @@ -0,0 +1,446 @@ +// Configuration +const API_BASE_URL = 'http://localhost:5000/api'; +const REFRESH_INTERVAL = 5000; // 5 seconds +const HISTORY_LENGTH = 20; // Keep last 20 data points + +// Global state +let charts = {}; +let historicalData = { + labels: [], + readSpeeds: {}, + writeSpeeds: {}, + readIOPS: {}, + writeIOPS: {} +}; + +// Initialize the dashboard +document.addEventListener('DOMContentLoaded', () => { + initializeCharts(); + fetchAllData(); + startAutoRefresh(); + + // Manual refresh button + document.getElementById('refresh-btn').addEventListener('click', () => { + fetchAllData(); + }); +}); + +// Initialize all charts +function initializeCharts() { + const chartConfig = { + responsive: true, + maintainAspectRatio: true, + plugins: { + legend: { + labels: { + color: '#e2e8f0' + } + } + }, + scales: { + y: { + beginAtZero: true, + ticks: { + color: '#94a3b8' + }, + grid: { + color: '#334155' + } + }, + x: { + ticks: { + color: '#94a3b8' + }, + grid: { + color: '#334155' + } + } + } + }; + + // Speed Chart + charts.speed = new Chart(document.getElementById('speed-chart'), { + type: 'bar', + data: { + labels: [], + datasets: [ + { + label: 'Read Speed (MB/s)', + data: [], + backgroundColor: 'rgba(37, 99, 235, 0.8)', + borderColor: 'rgba(37, 99, 235, 1)', + borderWidth: 1 + }, + { + label: 'Write Speed (MB/s)', + data: [], + backgroundColor: 'rgba(6, 182, 212, 0.8)', + borderColor: 'rgba(6, 182, 212, 1)', + borderWidth: 1 + } + ] + }, + options: chartConfig + }); + + // IOPS Chart + charts.iops = new Chart(document.getElementById('iops-chart'), { + type: 'bar', + data: { + labels: [], + datasets: [ + { + label: 'Read IOPS', + data: [], + backgroundColor: 'rgba(16, 185, 129, 0.8)', + borderColor: 'rgba(16, 185, 129, 1)', + borderWidth: 1 + }, + { + label: 'Write IOPS', + data: [], + backgroundColor: 'rgba(245, 158, 11, 0.8)', + borderColor: 'rgba(245, 158, 11, 1)', + borderWidth: 1 + } + ] + }, + options: chartConfig + }); + + // Historical Speed Chart + charts.historical = new Chart(document.getElementById('historical-chart'), { + type: 'line', + data: { + labels: [], + datasets: [] + }, + options: { + ...chartConfig, + elements: { + line: { + tension: 0.4 + }, + point: { + radius: 3 + } + } + } + }); + + // Historical IOPS Chart + charts.iopsTrend = new Chart(document.getElementById('iops-trend-chart'), { + type: 'line', + data: { + labels: [], + datasets: [] + }, + options: { + ...chartConfig, + elements: { + line: { + tension: 0.4 + }, + point: { + radius: 3 + } + } + } + }); +} + +// Fetch all data +async function fetchAllData() { + try { + const response = await fetch(`${API_BASE_URL}/metrics`); + if (!response.ok) throw new Error('Failed to fetch metrics'); + + const data = await response.json(); + + updateSystemInfo(data.system_info); + updatePartitions(data.partitions); + updateIOStats(data.io_stats); + updateSMARTData(data.smart_data); + updateHistoricalData(data.io_stats); + updateLastUpdateTime(); + } catch (error) { + console.error('Error fetching data:', error); + showError('Failed to fetch data from server. Make sure the backend is running.'); + } +} + +// Update system information +function updateSystemInfo(info) { + if (!info) return; + + document.getElementById('platform').textContent = + `${info.platform} ${info.platform_release}`; + document.getElementById('hostname').textContent = info.hostname; + document.getElementById('uptime').textContent = + `${info.uptime_days}d ${info.uptime_hours}h ${info.uptime_minutes}m`; + document.getElementById('boot-time').textContent = info.boot_time; +} + +// Update disk partitions +function updatePartitions(partitions) { + if (!partitions || partitions.length === 0) { + document.getElementById('partitions-container').innerHTML = + '
No partitions found
'; + return; + } + + const container = document.getElementById('partitions-container'); + container.innerHTML = ''; + + partitions.forEach(partition => { + const card = document.createElement('div'); + card.className = 'partition-card'; + + let progressClass = ''; + if (partition.percent >= 90) progressClass = 'danger'; + else if (partition.percent >= 75) progressClass = 'warning'; + + card.innerHTML = ` +
+
${partition.device}
+
${partition.fstype}
+
+
+
+ Mount Point: + ${partition.mountpoint} +
+
+ Total: + ${partition.total_gb} GB +
+
+ Used: + ${partition.used_gb} GB +
+
+ Free: + ${partition.free_gb} GB +
+
+
+
+ ${partition.percent.toFixed(1)}% +
+
+ `; + + container.appendChild(card); + }); +} + +// Update I/O statistics +function updateIOStats(ioStats) { + if (!ioStats || ioStats.length === 0) { + document.getElementById('io-stats-body').innerHTML = + 'No I/O data available'; + return; + } + + // Update table + const tbody = document.getElementById('io-stats-body'); + tbody.innerHTML = ''; + + ioStats.forEach(stat => { + const row = document.createElement('tr'); + row.innerHTML = ` + ${stat.disk} + ${stat.read_speed_mbps.toFixed(2)} MB/s + ${stat.write_speed_mbps.toFixed(2)} MB/s + ${stat.iops_read.toFixed(0)} + ${stat.iops_write.toFixed(0)} + ${stat.read_mb.toFixed(2)} MB + ${stat.write_mb.toFixed(2)} MB + `; + tbody.appendChild(row); + }); + + // Update bar charts + const labels = ioStats.map(s => s.disk); + const readSpeeds = ioStats.map(s => s.read_speed_mbps); + const writeSpeeds = ioStats.map(s => s.write_speed_mbps); + const readIOPS = ioStats.map(s => s.iops_read); + const writeIOPS = ioStats.map(s => s.iops_write); + + charts.speed.data.labels = labels; + charts.speed.data.datasets[0].data = readSpeeds; + charts.speed.data.datasets[1].data = writeSpeeds; + charts.speed.update(); + + charts.iops.data.labels = labels; + charts.iops.data.datasets[0].data = readIOPS; + charts.iops.data.datasets[1].data = writeIOPS; + charts.iops.update(); +} + +// Update SMART data +function updateSMARTData(smartData) { + const container = document.getElementById('smart-container'); + + if (!smartData || (Array.isArray(smartData) && smartData.length === 0)) { + container.innerHTML = + '
No SMART data available (requires root/admin privileges)
'; + return; + } + + if (smartData.error) { + container.innerHTML = + `
SMART data unavailable: ${smartData.message}
`; + return; + } + + container.innerHTML = ''; + + smartData.forEach(device => { + const card = document.createElement('div'); + card.className = 'smart-card'; + + let healthClass = 'good'; + let healthText = device.health || 'Unknown'; + if (healthText.toLowerCase().includes('fail')) healthClass = 'bad'; + else if (healthText.toLowerCase().includes('warn')) healthClass = 'warning'; + + card.innerHTML = ` +
+
${device.name}
+
${healthText}
+
+
+
+ Model: + ${device.model || 'N/A'} +
+
+ Serial: + ${device.serial || 'N/A'} +
+
+ Capacity: + ${device.capacity || 'N/A'} +
+
+ Temperature: + ${device.temperature ? device.temperature + '°C' : 'N/A'} +
+
+ Power On Hours: + ${device.power_on_hours || 'N/A'} +
+
+ Power Cycles: + ${device.power_cycle_count || 'N/A'} +
+
+ `; + + container.appendChild(card); + }); +} + +// Update historical data +function updateHistoricalData(ioStats) { + if (!ioStats || ioStats.length === 0) return; + + const now = new Date().toLocaleTimeString(); + historicalData.labels.push(now); + + ioStats.forEach(stat => { + if (!historicalData.readSpeeds[stat.disk]) { + historicalData.readSpeeds[stat.disk] = []; + historicalData.writeSpeeds[stat.disk] = []; + historicalData.readIOPS[stat.disk] = []; + historicalData.writeIOPS[stat.disk] = []; + } + + historicalData.readSpeeds[stat.disk].push(stat.read_speed_mbps); + historicalData.writeSpeeds[stat.disk].push(stat.write_speed_mbps); + historicalData.readIOPS[stat.disk].push(stat.iops_read); + historicalData.writeIOPS[stat.disk].push(stat.iops_write); + }); + + // Keep only last N data points + if (historicalData.labels.length > HISTORY_LENGTH) { + historicalData.labels.shift(); + Object.keys(historicalData.readSpeeds).forEach(disk => { + historicalData.readSpeeds[disk].shift(); + historicalData.writeSpeeds[disk].shift(); + historicalData.readIOPS[disk].shift(); + historicalData.writeIOPS[disk].shift(); + }); + } + + updateHistoricalCharts(); +} + +// Update historical charts +function updateHistoricalCharts() { + const colors = [ + 'rgba(37, 99, 235, 1)', + 'rgba(6, 182, 212, 1)', + 'rgba(16, 185, 129, 1)', + 'rgba(245, 158, 11, 1)', + 'rgba(239, 68, 68, 1)', + 'rgba(168, 85, 247, 1)' + ]; + + // Speed trend chart + charts.historical.data.labels = historicalData.labels; + charts.historical.data.datasets = []; + + let colorIndex = 0; + Object.keys(historicalData.readSpeeds).forEach(disk => { + charts.historical.data.datasets.push({ + label: `${disk} Read`, + data: historicalData.readSpeeds[disk], + borderColor: colors[colorIndex % colors.length], + backgroundColor: colors[colorIndex % colors.length].replace('1)', '0.1)'), + borderWidth: 2, + fill: false + }); + colorIndex++; + }); + + charts.historical.update(); + + // IOPS trend chart + charts.iopsTrend.data.labels = historicalData.labels; + charts.iopsTrend.data.datasets = []; + + colorIndex = 0; + Object.keys(historicalData.readIOPS).forEach(disk => { + charts.iopsTrend.data.datasets.push({ + label: `${disk} Read IOPS`, + data: historicalData.readIOPS[disk], + borderColor: colors[colorIndex % colors.length], + backgroundColor: colors[colorIndex % colors.length].replace('1)', '0.1)'), + borderWidth: 2, + fill: false + }); + colorIndex++; + }); + + charts.iopsTrend.update(); +} + +// Update last update time +function updateLastUpdateTime() { + const now = new Date(); + document.getElementById('last-update').textContent = + `Last Update: ${now.toLocaleTimeString()}`; +} + +// Start auto-refresh +function startAutoRefresh() { + setInterval(fetchAllData, REFRESH_INTERVAL); +} + +// Show error message +function showError(message) { + console.error(message); + // You could add a toast notification here +} diff --git a/Storage Dashboard/frontend/index.html b/Storage Dashboard/frontend/index.html new file mode 100644 index 00000000..0f62a82c --- /dev/null +++ b/Storage Dashboard/frontend/index.html @@ -0,0 +1,121 @@ + + + + + + Storage Device Performance Dashboard + + + + +
+
+

Storage Device Performance Dashboard

+
+ Last Update: Never + +
+
+ + +
+

System Information

+
+
+ Platform: + Loading... +
+
+ Hostname: + Loading... +
+
+ Uptime: + Loading... +
+
+ Boot Time: + Loading... +
+
+
+ + +
+

Disk Partitions & Usage

+
+
Loading partition data...
+
+
+ + +
+

I/O Performance Metrics

+
+
+

Read/Write Speed (MB/s)

+ +
+
+

IOPS (Operations/sec)

+ +
+
+
+ + +
+

Detailed I/O Statistics

+
+ + + + + + + + + + + + + + + + + +
DiskRead SpeedWrite SpeedRead IOPSWrite IOPSTotal ReadTotal Write
Loading I/O statistics...
+
+
+ + +
+

SMART Health Data

+
+
Loading SMART data...
+
+
+ + +
+

Historical Performance

+
+
+

Read/Write Trends

+ +
+
+

IOPS Trends

+ +
+
+
+ +
+

Auto-refresh every 5 seconds | Data updates in real-time

+
+
+ + + + diff --git a/Storage Dashboard/frontend/styles.css b/Storage Dashboard/frontend/styles.css new file mode 100644 index 00000000..441a8055 --- /dev/null +++ b/Storage Dashboard/frontend/styles.css @@ -0,0 +1,386 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --primary-color: #2563eb; + --secondary-color: #1e40af; + --success-color: #10b981; + --warning-color: #f59e0b; + --danger-color: #ef4444; + --bg-color: #0f172a; + --card-bg: #1e293b; + --text-color: #e2e8f0; + --text-muted: #94a3b8; + --border-color: #334155; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; + background: var(--bg-color); + color: var(--text-color); + line-height: 1.6; +} + +.container { + max-width: 1400px; + margin: 0 auto; + padding: 20px; +} + +header { + background: var(--card-bg); + padding: 25px; + border-radius: 10px; + margin-bottom: 30px; + display: flex; + justify-content: space-between; + align-items: center; + border: 1px solid var(--border-color); +} + +header h1 { + font-size: 28px; + background: linear-gradient(135deg, var(--primary-color), #06b6d4); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.header-info { + display: flex; + align-items: center; + gap: 20px; +} + +#last-update { + color: var(--text-muted); + font-size: 14px; +} + +.btn { + background: var(--primary-color); + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + cursor: pointer; + font-size: 14px; + font-weight: 500; + transition: all 0.3s ease; +} + +.btn:hover { + background: var(--secondary-color); + transform: translateY(-1px); +} + +.btn:active { + transform: translateY(0); +} + +.section { + background: var(--card-bg); + padding: 25px; + border-radius: 10px; + margin-bottom: 30px; + border: 1px solid var(--border-color); +} + +.section h2 { + font-size: 22px; + margin-bottom: 20px; + color: var(--text-color); + border-bottom: 2px solid var(--border-color); + padding-bottom: 10px; +} + +.section h3 { + font-size: 16px; + margin-bottom: 15px; + color: var(--text-muted); + font-weight: 500; +} + +.system-info-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 15px; +} + +.info-card { + background: var(--bg-color); + padding: 15px; + border-radius: 8px; + display: flex; + justify-content: space-between; + align-items: center; + border: 1px solid var(--border-color); +} + +.info-card .label { + color: var(--text-muted); + font-size: 14px; + font-weight: 500; +} + +.info-card .value { + color: var(--text-color); + font-size: 14px; + font-weight: 600; +} + +.partitions-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + gap: 20px; +} + +.partition-card { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.partition-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; +} + +.partition-name { + font-weight: 600; + font-size: 16px; + color: var(--primary-color); +} + +.partition-type { + background: var(--border-color); + padding: 4px 10px; + border-radius: 4px; + font-size: 12px; + color: var(--text-muted); +} + +.partition-info { + display: flex; + flex-direction: column; + gap: 8px; + margin-bottom: 15px; +} + +.partition-info-row { + display: flex; + justify-content: space-between; + font-size: 13px; +} + +.partition-info-row .label { + color: var(--text-muted); +} + +.partition-info-row .value { + color: var(--text-color); + font-weight: 500; +} + +.progress-bar { + width: 100%; + height: 24px; + background: var(--border-color); + border-radius: 12px; + overflow: hidden; + position: relative; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, var(--primary-color), #06b6d4); + transition: width 0.5s ease; + display: flex; + align-items: center; + justify-content: center; + font-size: 12px; + font-weight: 600; +} + +.progress-fill.warning { + background: linear-gradient(90deg, var(--warning-color), #fbbf24); +} + +.progress-fill.danger { + background: linear-gradient(90deg, var(--danger-color), #f87171); +} + +.charts-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 20px; +} + +.chart-container { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.chart-container canvas { + max-height: 300px; +} + +.table-container { + overflow-x: auto; +} + +table { + width: 100%; + border-collapse: collapse; +} + +thead { + background: var(--bg-color); +} + +th { + padding: 12px; + text-align: left; + font-weight: 600; + font-size: 14px; + color: var(--text-muted); + border-bottom: 2px solid var(--border-color); +} + +td { + padding: 12px; + font-size: 14px; + border-bottom: 1px solid var(--border-color); +} + +tbody tr:hover { + background: var(--bg-color); +} + +.smart-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); + gap: 20px; +} + +.smart-card { + background: var(--bg-color); + padding: 20px; + border-radius: 8px; + border: 1px solid var(--border-color); +} + +.smart-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + padding-bottom: 10px; + border-bottom: 1px solid var(--border-color); +} + +.smart-model { + font-weight: 600; + font-size: 16px; +} + +.health-badge { + padding: 6px 12px; + border-radius: 6px; + font-size: 12px; + font-weight: 600; +} + +.health-badge.good { + background: var(--success-color); + color: white; +} + +.health-badge.warning { + background: var(--warning-color); + color: white; +} + +.health-badge.bad { + background: var(--danger-color); + color: white; +} + +.smart-details { + display: flex; + flex-direction: column; + gap: 8px; +} + +.smart-row { + display: flex; + justify-content: space-between; + font-size: 13px; +} + +.smart-row .label { + color: var(--text-muted); +} + +.smart-row .value { + color: var(--text-color); + font-weight: 500; +} + +.loading { + text-align: center; + padding: 30px; + color: var(--text-muted); +} + +.info-message { + padding: 20px; + background: var(--bg-color); + border-radius: 8px; + text-align: center; + color: var(--text-muted); + border: 1px solid var(--border-color); +} + +.error-message { + padding: 15px; + background: rgba(239, 68, 68, 0.1); + border: 1px solid var(--danger-color); + border-radius: 8px; + color: var(--danger-color); + margin-top: 10px; +} + +footer { + text-align: center; + padding: 20px; + color: var(--text-muted); + font-size: 14px; +} + +@media (max-width: 768px) { + header { + flex-direction: column; + gap: 15px; + text-align: center; + } + + .header-info { + flex-direction: column; + gap: 10px; + } + + .charts-grid, + .partitions-grid, + .smart-grid { + grid-template-columns: 1fr; + } +} diff --git a/Storage Dashboard/start.bat b/Storage Dashboard/start.bat new file mode 100644 index 00000000..45fa6613 --- /dev/null +++ b/Storage Dashboard/start.bat @@ -0,0 +1,45 @@ +@echo off +REM Storage Dashboard Start Script for Windows + +echo ================================ +echo Storage Dashboard Startup +echo ================================ +echo. + +REM Check if Python is installed +python --version >nul 2>&1 +if errorlevel 1 ( + echo Error: Python is not installed + pause + exit /b 1 +) + +echo Python version: +python --version +echo. + +REM Navigate to backend directory +cd backend + +REM Check if requirements are installed +echo Checking dependencies... +python -c "import flask" >nul 2>&1 +if errorlevel 1 ( + echo Installing dependencies... + pip install -r requirements.txt +) else ( + echo Dependencies already installed +) + +echo. +echo ================================ +echo Starting Storage Dashboard... +echo ================================ +echo. +echo Access the dashboard at: http://localhost:5000 +echo. +echo Press Ctrl+C to stop the server +echo. + +REM Start the Flask application +python app.py diff --git a/Storage Dashboard/start.sh b/Storage Dashboard/start.sh new file mode 100755 index 00000000..46d9a1ac --- /dev/null +++ b/Storage Dashboard/start.sh @@ -0,0 +1,42 @@ +#!/bin/bash + +# Storage Dashboard Start Script + +echo "================================" +echo "Storage Dashboard Startup" +echo "================================" +echo "" + +# Check if Python is installed +if ! command -v python3 &> /dev/null; then + echo "Error: Python 3 is not installed" + exit 1 +fi + +echo "Python version: $(python3 --version)" +echo "" + +# Navigate to backend directory +cd backend + +# Check if requirements are installed +echo "Checking dependencies..." +if ! python3 -c "import flask" 2>/dev/null; then + echo "Installing dependencies..." + pip install -r requirements.txt +else + echo "Dependencies already installed" +fi + +echo "" +echo "================================" +echo "Starting Storage Dashboard..." +echo "================================" +echo "" +echo "Access the dashboard at: http://localhost:5000" +echo "" +echo "Press Ctrl+C to stop the server" +echo "" + +# Start the Flask application +python3 app.py