mirror of
https://github.com/x1xhlol/system-prompts-and-models-of-ai-tools.git
synced 2026-01-30 13:54:18 -05:00
Add comprehensive Storage Device Performance Dashboard
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 <noreply@anthropic.com>
This commit is contained in:
parent
dd8601ce16
commit
563a800fad
47
Storage Dashboard/.gitignore
vendored
Normal file
47
Storage Dashboard/.gitignore
vendored
Normal file
@ -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
|
||||||
334
Storage Dashboard/README.md
Normal file
334
Storage Dashboard/README.md
Normal file
@ -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.
|
||||||
|
|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
|
## 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**
|
||||||
70
Storage Dashboard/backend/app.py
Normal file
70
Storage Dashboard/backend/app.py
Normal file
@ -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('/<path:path>')
|
||||||
|
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)
|
||||||
149
Storage Dashboard/backend/metrics_collector.py
Normal file
149
Storage Dashboard/backend/metrics_collector.py
Normal file
@ -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()
|
||||||
|
}
|
||||||
4
Storage Dashboard/backend/requirements.txt
Normal file
4
Storage Dashboard/backend/requirements.txt
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
flask==3.0.0
|
||||||
|
flask-cors==4.0.0
|
||||||
|
psutil==5.9.6
|
||||||
|
pySMART==1.3.0
|
||||||
446
Storage Dashboard/frontend/dashboard.js
Normal file
446
Storage Dashboard/frontend/dashboard.js
Normal file
@ -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 =
|
||||||
|
'<div class="info-message">No partitions found</div>';
|
||||||
|
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 = `
|
||||||
|
<div class="partition-header">
|
||||||
|
<div class="partition-name">${partition.device}</div>
|
||||||
|
<div class="partition-type">${partition.fstype}</div>
|
||||||
|
</div>
|
||||||
|
<div class="partition-info">
|
||||||
|
<div class="partition-info-row">
|
||||||
|
<span class="label">Mount Point:</span>
|
||||||
|
<span class="value">${partition.mountpoint}</span>
|
||||||
|
</div>
|
||||||
|
<div class="partition-info-row">
|
||||||
|
<span class="label">Total:</span>
|
||||||
|
<span class="value">${partition.total_gb} GB</span>
|
||||||
|
</div>
|
||||||
|
<div class="partition-info-row">
|
||||||
|
<span class="label">Used:</span>
|
||||||
|
<span class="value">${partition.used_gb} GB</span>
|
||||||
|
</div>
|
||||||
|
<div class="partition-info-row">
|
||||||
|
<span class="label">Free:</span>
|
||||||
|
<span class="value">${partition.free_gb} GB</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill ${progressClass}" style="width: ${partition.percent}%">
|
||||||
|
${partition.percent.toFixed(1)}%
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
container.appendChild(card);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update I/O statistics
|
||||||
|
function updateIOStats(ioStats) {
|
||||||
|
if (!ioStats || ioStats.length === 0) {
|
||||||
|
document.getElementById('io-stats-body').innerHTML =
|
||||||
|
'<tr><td colspan="7" class="loading">No I/O data available</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update table
|
||||||
|
const tbody = document.getElementById('io-stats-body');
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
|
||||||
|
ioStats.forEach(stat => {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
row.innerHTML = `
|
||||||
|
<td><strong>${stat.disk}</strong></td>
|
||||||
|
<td>${stat.read_speed_mbps.toFixed(2)} MB/s</td>
|
||||||
|
<td>${stat.write_speed_mbps.toFixed(2)} MB/s</td>
|
||||||
|
<td>${stat.iops_read.toFixed(0)}</td>
|
||||||
|
<td>${stat.iops_write.toFixed(0)}</td>
|
||||||
|
<td>${stat.read_mb.toFixed(2)} MB</td>
|
||||||
|
<td>${stat.write_mb.toFixed(2)} MB</td>
|
||||||
|
`;
|
||||||
|
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 =
|
||||||
|
'<div class="info-message">No SMART data available (requires root/admin privileges)</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (smartData.error) {
|
||||||
|
container.innerHTML =
|
||||||
|
`<div class="info-message">SMART data unavailable: ${smartData.message}</div>`;
|
||||||
|
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 = `
|
||||||
|
<div class="smart-header">
|
||||||
|
<div class="smart-model">${device.name}</div>
|
||||||
|
<div class="health-badge ${healthClass}">${healthText}</div>
|
||||||
|
</div>
|
||||||
|
<div class="smart-details">
|
||||||
|
<div class="smart-row">
|
||||||
|
<span class="label">Model:</span>
|
||||||
|
<span class="value">${device.model || 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="smart-row">
|
||||||
|
<span class="label">Serial:</span>
|
||||||
|
<span class="value">${device.serial || 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="smart-row">
|
||||||
|
<span class="label">Capacity:</span>
|
||||||
|
<span class="value">${device.capacity || 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="smart-row">
|
||||||
|
<span class="label">Temperature:</span>
|
||||||
|
<span class="value">${device.temperature ? device.temperature + '°C' : 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="smart-row">
|
||||||
|
<span class="label">Power On Hours:</span>
|
||||||
|
<span class="value">${device.power_on_hours || 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
<div class="smart-row">
|
||||||
|
<span class="label">Power Cycles:</span>
|
||||||
|
<span class="value">${device.power_cycle_count || 'N/A'}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
121
Storage Dashboard/frontend/index.html
Normal file
121
Storage Dashboard/frontend/index.html
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Storage Device Performance Dashboard</title>
|
||||||
|
<link rel="stylesheet" href="styles.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Storage Device Performance Dashboard</h1>
|
||||||
|
<div class="header-info">
|
||||||
|
<span id="last-update">Last Update: Never</span>
|
||||||
|
<button id="refresh-btn" class="btn">Refresh Now</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<!-- System Information -->
|
||||||
|
<section class="section">
|
||||||
|
<h2>System Information</h2>
|
||||||
|
<div class="system-info-grid" id="system-info">
|
||||||
|
<div class="info-card">
|
||||||
|
<span class="label">Platform:</span>
|
||||||
|
<span class="value" id="platform">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<span class="label">Hostname:</span>
|
||||||
|
<span class="value" id="hostname">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<span class="label">Uptime:</span>
|
||||||
|
<span class="value" id="uptime">Loading...</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-card">
|
||||||
|
<span class="label">Boot Time:</span>
|
||||||
|
<span class="value" id="boot-time">Loading...</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Disk Partitions -->
|
||||||
|
<section class="section">
|
||||||
|
<h2>Disk Partitions & Usage</h2>
|
||||||
|
<div id="partitions-container" class="partitions-grid">
|
||||||
|
<div class="loading">Loading partition data...</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- I/O Performance Charts -->
|
||||||
|
<section class="section">
|
||||||
|
<h2>I/O Performance Metrics</h2>
|
||||||
|
<div class="charts-grid">
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>Read/Write Speed (MB/s)</h3>
|
||||||
|
<canvas id="speed-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>IOPS (Operations/sec)</h3>
|
||||||
|
<canvas id="iops-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- I/O Statistics Table -->
|
||||||
|
<section class="section">
|
||||||
|
<h2>Detailed I/O Statistics</h2>
|
||||||
|
<div class="table-container">
|
||||||
|
<table id="io-stats-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Disk</th>
|
||||||
|
<th>Read Speed</th>
|
||||||
|
<th>Write Speed</th>
|
||||||
|
<th>Read IOPS</th>
|
||||||
|
<th>Write IOPS</th>
|
||||||
|
<th>Total Read</th>
|
||||||
|
<th>Total Write</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="io-stats-body">
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="loading">Loading I/O statistics...</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- SMART Data -->
|
||||||
|
<section class="section">
|
||||||
|
<h2>SMART Health Data</h2>
|
||||||
|
<div id="smart-container" class="smart-grid">
|
||||||
|
<div class="info-message">Loading SMART data...</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- Historical Data Charts -->
|
||||||
|
<section class="section">
|
||||||
|
<h2>Historical Performance</h2>
|
||||||
|
<div class="charts-grid">
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>Read/Write Trends</h3>
|
||||||
|
<canvas id="historical-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
<div class="chart-container">
|
||||||
|
<h3>IOPS Trends</h3>
|
||||||
|
<canvas id="iops-trend-chart"></canvas>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<footer>
|
||||||
|
<p>Auto-refresh every 5 seconds | Data updates in real-time</p>
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="dashboard.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
386
Storage Dashboard/frontend/styles.css
Normal file
386
Storage Dashboard/frontend/styles.css
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
Storage Dashboard/start.bat
Normal file
45
Storage Dashboard/start.bat
Normal file
@ -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
|
||||||
42
Storage Dashboard/start.sh
Executable file
42
Storage Dashboard/start.sh
Executable file
@ -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
|
||||||
Loading…
Reference in New Issue
Block a user