diff --git a/.gitignore b/.gitignore
index a630026..c2c688d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,7 @@ Thumbs.db
# IDE files
.vscode/
.idea/
+.claude/
*.swp
*.swo
*~
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..108a963
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,7 @@
+FROM nginx:alpine
+COPY index.html /usr/share/nginx/html/
+COPY styles.css /usr/share/nginx/html/
+COPY script.js /usr/share/nginx/html/
+COPY config.js /usr/share/nginx/html/
+RUN chmod 644 /usr/share/nginx/html/*.html /usr/share/nginx/html/*.css /usr/share/nginx/html/*.js
+EXPOSE 80
diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md
new file mode 100644
index 0000000..d635638
--- /dev/null
+++ b/IMPROVEMENTS.md
@@ -0,0 +1,680 @@
+# Dashboard Improvement Ideas
+
+This document contains recommended improvements for the Thanos Systems Monitor dashboard.
+
+## Status Legend
+- ✅ **Completed**: Implementation finished
+- 🔄 **In Progress**: Currently being worked on
+- ⏳ **Pending**: Not yet started
+- 💡 **Proposed**: Idea for consideration
+
+---
+
+## 1. Security Concerns ✅
+
+**Status**: COMPLETED (2025-11-22)
+
+**Implementation**:
+- ✅ Created separate `config.js` file for API credentials
+- ✅ Added `config.js` to `.gitignore`
+- ✅ Created `config.example.js` template
+- ✅ Updated `script.js` to remove hardcoded credentials
+- ✅ Added validation check for missing configuration
+
+---
+
+## 2. Error Handling and User Feedback ⏳
+
+**Current Limitation**: The `showError()` function at `script.js:479-485` only displays errors in the timestamp element, which does not exist in your HTML.
+
+**Proposed Improvements**:
+
+### 2.1 Add Timestamp and Error Display
+- Add timestamp element to header showing last update time
+- Create dedicated error notification system
+- Style error notifications with cyberpunk theme
+
+### 2.2 Enhanced Error Handling
+- Implement retry logic with exponential backoff when API requests fail
+- Display connection status indicator (online/offline/connecting)
+- Show loading states during initial data fetch
+- Add user-friendly error messages for common failures
+
+### 2.3 Implementation Details
+```javascript
+// Retry logic with exponential backoff
+async function fetchWithRetry(fetchFunction, maxRetries = 3) {
+ for (let i = 0; i < maxRetries; i++) {
+ try {
+ return await fetchFunction();
+ } catch (error) {
+ if (i === maxRetries - 1) throw error;
+ await new Promise(resolve => setTimeout(resolve, Math.pow(2, i) * 1000));
+ }
+ }
+}
+
+// Connection status indicator
+function updateConnectionStatus(status) {
+ // 'online', 'offline', 'connecting', 'error'
+}
+```
+
+---
+
+## 3. Data Visualization Enhancements ✅
+
+### 3.1 Ring Chart Improvements
+**Status**: COMPLETED (2025-11-22)
+
+**Implemented Features**:
+- ✅ Added smooth CSS transitions for ring chart value changes
+- ✅ Display trend indicators (▲/▼/━ showing increasing/decreasing/stable usage)
+- ✅ Added hover tooltips showing detailed information (Used, Free, Total, Usage %)
+- ✅ Implemented historical data tracking (last 20 data points per metric)
+- ✅ Enhanced hover effects with scale transformation
+- ✅ Refactored code to use generic updateRingChartGeneric() function, reducing duplication
+
+**Implementation Details**:
+- Created HistoricalData object to track CPU, Memory, and individual disk usage over time
+- Added createTrendIndicator() helper function for visual trend display
+- Added createTooltip() helper function for consistent tooltip formatting
+- Tooltips display on hover with fade-in animation
+- Trend calculation based on last 3 data points with 1% threshold for stability
+
+### 3.2 Disk Array Enhancements
+**Status**: COMPLETED (2025-11-22)
+
+**Implemented Features**:
+- ✅ Added hover tooltips for each disk showing capacity, temperature, usage, and status
+- ✅ Added trend indicators to disk progress bars (▲/▼/━)
+- ✅ Enhanced hover effects (background color change, border color change)
+- ✅ Historical data tracking for individual disks
+
+**Remaining Enhancements**:
+- ⏳ Visual indicators for disk health status (SMART data if available from API)
+- ⏳ Show read/write speed metrics if available from API
+- ⏳ Group disks by function (parity, cache, data) more clearly
+- ⏳ Add disk age/hours powered on if available from API
+
+### 3.3 Historical Data Graphs
+**New Feature**:
+- Line graphs for CPU usage over time (last hour/day)
+- Memory usage trends
+- Disk I/O graphs
+- Network traffic visualization
+- Implement using HTML5 Canvas or lightweight charting library
+
+---
+
+## 4. Performance Optimizations ⏳
+
+**Current Implementation**: Updates occur every 5 seconds regardless of visibility
+
+### 4.1 Page Visibility API
+```javascript
+document.addEventListener('visibilitychange', () => {
+ if (document.hidden) {
+ // Pause updates
+ clearInterval(updateInterval);
+ } else {
+ // Resume updates
+ updateInterval = setInterval(updateDashboard, 5000);
+ updateDashboard(); // Immediate update on return
+ }
+});
+```
+
+### 4.2 Animation Optimization
+- Use `requestAnimationFrame` for smoother animations
+- Batch DOM updates to minimize reflows
+- Use CSS transforms instead of position changes
+
+### 4.3 Differential Updates
+- Only update DOM elements that have changed values
+- Compare previous state with current state
+- Reduce unnecessary re-renders
+
+### 4.4 Web Workers
+- Move data processing to Web Worker
+- Keep UI thread responsive
+- Process API responses in background
+
+---
+
+## 5. Code Quality Improvements ⏳
+
+### 5.1 Eliminate Code Duplication
+
+**Current Issue**: Ring chart update functions (`script.js:113-240`) contain significant duplication
+
+**Refactoring Recommendation**:
+```javascript
+function updateRingChart(elementId, percentage, usedKB, totalKB) {
+ const ring = document.getElementById(`${elementId}-ring`);
+ const text = document.getElementById(`${elementId}-percentage`);
+ const fraction = document.getElementById(`${elementId}-fraction`);
+
+ const circumference = 2 * Math.PI * 80;
+ const offset = circumference - (percentage / 100) * circumference;
+
+ ring.style.strokeDashoffset = offset;
+ text.textContent = percentage + '%';
+
+ const usedTB = (usedKB / 1024 / 1024 / 1024).toFixed(2);
+ const totalTB = (totalKB / 1024 / 1024 / 1024).toFixed(2);
+ fraction.textContent = `${usedTB} / ${totalTB} TB`;
+
+ // Apply color coding
+ applyThresholdColors(ring, text, fraction, percentage);
+}
+
+function applyThresholdColors(ring, text, fraction, percentage) {
+ const color = percentage >= 90 ? '#ffffff' :
+ percentage >= 70 ? '#ff00ff' : '#00ffff';
+
+ ring.style.stroke = color;
+ text.style.fill = color;
+ fraction.style.fill = color;
+}
+
+// Usage
+updateRingChart('disk', totalUsagePercentage, totalUsed, totalCapacity);
+updateRingChart('docker', dockerUsagePercentage, dockerUsed, dockerCapacity);
+updateRingChart('photos', photosUsagePercentage, photosUsed, photosCapacity);
+updateRingChart('media', mediaUsagePercentage, mediaUsed, mediaCapacity);
+```
+
+### 5.2 Extract Constants
+```javascript
+// Configuration constants
+const CONSTANTS = {
+ UPDATE_INTERVAL: 5000,
+ RING_RADIUS: 80,
+ WARNING_THRESHOLD: 70,
+ CRITICAL_THRESHOLD: 90,
+ COLORS: {
+ PRIMARY: '#00ffff',
+ WARNING: '#ff00ff',
+ CRITICAL: '#ffffff',
+ BACKGROUND: '#1a1a1a'
+ }
+};
+```
+
+### 5.3 Add JSDoc Documentation
+```javascript
+/**
+ * Updates a ring chart with usage data
+ * @param {string} elementId - The base ID for the ring chart elements
+ * @param {number} percentage - Usage percentage (0-100)
+ * @param {number} usedKB - Used space in kilobytes
+ * @param {number} totalKB - Total space in kilobytes
+ */
+function updateRingChart(elementId, percentage, usedKB, totalKB) {
+ // Implementation
+}
+```
+
+### 5.4 Error Boundaries
+- Add try-catch blocks around each update function
+- Prevent one failing component from breaking entire dashboard
+- Log errors for debugging
+
+### 5.5 Data Validation
+```javascript
+function validateMetricsData(data) {
+ if (!data || typeof data !== 'object') {
+ throw new Error('Invalid metrics data');
+ }
+
+ if (data.cpu && typeof data.cpu.percentTotal !== 'number') {
+ console.warn('Invalid CPU data:', data.cpu);
+ }
+
+ return data;
+}
+```
+
+---
+
+## 6. Responsive Design Enhancements ⏳
+
+**Current State**: Basic responsive design at 768px breakpoint
+
+### 6.1 Additional Breakpoints
+```css
+/* Tablet landscape */
+@media (max-width: 1024px) {
+ .grid-row-1 {
+ grid-template-columns: 1fr;
+ }
+
+ .disk-usage-layout {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Tablet portrait */
+@media (max-width: 768px) {
+ .ring-chart-row-split {
+ grid-template-columns: 1fr;
+ }
+}
+
+/* Mobile */
+@media (max-width: 480px) {
+ .mini-ring-chart {
+ width: 120px;
+ height: 120px;
+ }
+
+ .title {
+ font-size: 1.5rem;
+ }
+}
+```
+
+### 6.2 Raspberry Pi Display Optimization
+- Test on actual Raspberry Pi display resolution
+- Optimize for common resolutions: 1920x1080, 1280x720
+- Consider touch-friendly interface elements
+- Test browser performance on Raspberry Pi 3
+
+### 6.3 Orientation Support
+- Handle landscape and portrait orientations
+- Reflow layout appropriately for vertical displays
+
+---
+
+## 7. Accessibility Improvements ⏳
+
+**Current State**: Limited accessibility features
+
+### 7.1 ARIA Labels and Roles
+```html
+
+
+
+
+
+ PARITY STATUS:
+ VALID
+
+```
+
+### 7.2 Live Regions
+```html
+
+
+
+
+```
+
+### 7.3 Keyboard Navigation
+- Add tab index to interactive elements
+- Implement keyboard shortcuts for common actions
+- Ensure focus indicators are visible
+
+### 7.4 Color Contrast
+- Verify all text meets WCAG AA standards (4.5:1 ratio)
+- Current cyan (#00ffff) on black (#000000) has 15.3:1 ratio ✓
+- Test magenta (#ff00ff) and white (#ffffff) combinations
+- Consider adding high contrast mode option
+
+### 7.5 Screen Reader Support
+```javascript
+function announceUpdate(message) {
+ const announcer = document.getElementById('status-announcer');
+ announcer.textContent = message;
+}
+
+// Usage
+announceUpdate('CPU usage increased to 85%');
+announceUpdate('Docker container "plex" started');
+```
+
+---
+
+## 8. Additional Features 💡
+
+### 8.1 User Preferences
+**Proposed Implementation**:
+```javascript
+const UserPreferences = {
+ updateInterval: 5000,
+ theme: 'dark', // Future: 'light' option
+ showAnimations: true,
+ temperatureUnit: 'celsius', // or 'fahrenheit'
+ notifications: {
+ enabled: true,
+ cpuThreshold: 90,
+ memoryThreshold: 90,
+ diskThreshold: 90
+ }
+};
+
+// Save to localStorage
+function savePreferences(prefs) {
+ localStorage.setItem('dashboardPrefs', JSON.stringify(prefs));
+}
+
+// Load from localStorage
+function loadPreferences() {
+ const saved = localStorage.getItem('dashboardPrefs');
+ return saved ? JSON.parse(saved) : UserPreferences;
+}
+```
+
+### 8.2 Configurable Alerts
+- Set custom thresholds for warnings
+- Browser notifications for critical states
+- Email/webhook notifications (requires backend)
+- Sound alerts for critical conditions
+
+### 8.3 Data Export
+```javascript
+function exportData(format = 'json') {
+ const data = {
+ timestamp: new Date().toISOString(),
+ cpu: currentCPUUsage,
+ memory: currentMemoryUsage,
+ disks: currentDiskData,
+ docker: currentDockerData
+ };
+
+ if (format === 'json') {
+ const blob = new Blob([JSON.stringify(data, null, 2)],
+ { type: 'application/json' });
+ downloadFile(blob, `unraid-metrics-${Date.now()}.json`);
+ } else if (format === 'csv') {
+ // Convert to CSV and download
+ }
+}
+```
+
+### 8.4 Theme Options
+- Light theme option
+- Custom color schemes
+- Adjustable glow intensity
+- Disable animations option (for performance)
+
+### 8.5 Network Monitoring
+**If Available from API**:
+- Network interface statistics
+- Upload/download speeds
+- Total bandwidth usage
+- Per-container network usage
+
+### 8.6 UPS Status
+**If Available from API**:
+- Battery charge level
+- Runtime remaining
+- Power status (on battery/on AC)
+- UPS load percentage
+
+### 8.7 System Temperature Monitoring
+**If Available from API**:
+- CPU temperature
+- Motherboard temperature
+- Additional thermal sensors
+- Temperature history graphs
+
+### 8.8 Cache Drive Monitoring
+- Separate cache drive statistics
+- Cache utilization
+- Mover status and schedule
+
+---
+
+## 9. Docker Container Enhancements ⏳
+
+**Current Display**: Basic name and status
+
+### 9.1 Additional Container Information
+**Proposed Additions**:
+- CPU usage per container (if available)
+- Memory usage per container (if available)
+- Port mappings display
+- Container uptime
+- Container health check status
+- Image version/tag
+
+### 9.2 Container Controls
+**If API Supports**:
+```javascript
+async function controlContainer(containerId, action) {
+ // action: 'start', 'stop', 'restart'
+ const query = `mutation {
+ dockerContainerControl(id: "${containerId}", action: "${action}") {
+ success
+ message
+ }
+ }`;
+
+ return await executeGraphQLQuery(query);
+}
+```
+
+**UI Implementation**:
+- Add start/stop/restart buttons to each container card
+- Confirmation dialog for destructive actions
+- Loading state during action execution
+- Success/error feedback
+
+### 9.3 Container Details Modal
+- Click container to view detailed information
+- Show logs (last 100 lines)
+- Environment variables
+- Volume mounts
+- Network configuration
+
+---
+
+## 10. Testing and Monitoring ⏳
+
+### 10.1 Unit Tests
+**Proposed Framework**: Jest or Vitest (lightweight)
+
+```javascript
+// Example test file: script.test.js
+describe('formatBytes', () => {
+ test('formats bytes correctly', () => {
+ expect(formatBytes(0)).toBe('0 B');
+ expect(formatBytes(1024)).toBe('1 KB');
+ expect(formatBytes(1048576)).toBe('1 MB');
+ expect(formatBytes(1073741824)).toBe('1 GB');
+ });
+});
+
+describe('updateCPU', () => {
+ test('applies critical class above 90%', () => {
+ document.body.innerHTML = `
+
+
+ `;
+
+ updateCPU(95);
+
+ const progressBar = document.getElementById('cpu-progress');
+ expect(progressBar.classList.contains('critical')).toBe(true);
+ });
+});
+```
+
+### 10.2 Integration Tests
+**Test API Integration**:
+```javascript
+describe('API Integration', () => {
+ test('fetches system metrics successfully', async () => {
+ const metrics = await fetchSystemMetrics();
+ expect(metrics).toHaveProperty('cpu');
+ expect(metrics).toHaveProperty('memory');
+ });
+
+ test('handles API errors gracefully', async () => {
+ // Mock failed request
+ global.fetch = jest.fn(() => Promise.reject('API Error'));
+
+ await expect(fetchSystemMetrics()).rejects.toThrow();
+ });
+});
+```
+
+### 10.3 Performance Monitoring
+```javascript
+// Add performance tracking
+const performanceMetrics = {
+ apiCallDuration: [],
+ renderDuration: [],
+ totalUpdateDuration: []
+};
+
+async function updateDashboard() {
+ const startTime = performance.now();
+
+ try {
+ // Existing update logic
+ } finally {
+ const duration = performance.now() - startTime;
+ performanceMetrics.totalUpdateDuration.push(duration);
+
+ // Log if update takes too long
+ if (duration > 1000) {
+ console.warn(`Slow update: ${duration}ms`);
+ }
+ }
+}
+```
+
+### 10.4 Error Logging
+**Client-Side Error Tracking**:
+```javascript
+// Simple error logger
+const ErrorLogger = {
+ errors: [],
+
+ log(error, context = {}) {
+ const errorEntry = {
+ timestamp: new Date().toISOString(),
+ message: error.message,
+ stack: error.stack,
+ context: context
+ };
+
+ this.errors.push(errorEntry);
+ console.error('Error logged:', errorEntry);
+
+ // Optional: Send to remote logging service
+ // this.sendToRemote(errorEntry);
+ },
+
+ getErrors() {
+ return this.errors;
+ },
+
+ clearErrors() {
+ this.errors = [];
+ }
+};
+
+// Global error handler
+window.addEventListener('error', (event) => {
+ ErrorLogger.log(event.error, {
+ type: 'uncaught',
+ filename: event.filename,
+ lineno: event.lineno
+ });
+});
+```
+
+### 10.5 Health Check Endpoint
+**If you add a service worker**:
+```javascript
+// Monitor dashboard health
+function dashboardHealthCheck() {
+ return {
+ status: 'healthy',
+ lastUpdate: lastUpdateTimestamp,
+ apiConnected: apiConnectionStatus,
+ errorCount: ErrorLogger.errors.length,
+ uptime: performance.now()
+ };
+}
+```
+
+---
+
+## 11. Advanced Features 💡
+
+### 11.1 Mobile App
+- Progressive Web App (PWA) support
+- Add manifest.json
+- Service worker for offline capability
+- App install prompt
+
+### 11.2 Multi-Server Support
+- Monitor multiple Unraid servers
+- Server selector dropdown
+- Aggregate statistics
+- Server comparison view
+
+### 11.3 Custom Widgets
+- Drag-and-drop dashboard customization
+- User-configurable layout
+- Widget library (add/remove sections)
+- Save custom layouts
+
+### 11.4 Scheduled Reports
+- Daily/weekly summary emails
+- PDF report generation
+- Usage trends analysis
+- Capacity planning recommendations
+
+---
+
+## Implementation Priority
+
+### High Priority (Core Functionality)
+1. ✅ Security improvements
+2. Error handling and user feedback
+3. Code quality improvements (reduce duplication)
+4. Performance optimizations (Page Visibility API)
+
+### Medium Priority (User Experience)
+5. Docker container enhancements
+6. Data visualization improvements
+7. Responsive design enhancements
+8. Accessibility improvements
+
+### Low Priority (Nice to Have)
+9. Additional features (themes, preferences)
+10. Testing infrastructure
+11. Advanced features (PWA, multi-server)
+
+---
+
+## Notes
+
+- All improvements should maintain the cyberpunk aesthetic
+- Keep implementation lightweight for Raspberry Pi 3 performance
+- Test thoroughly on actual hardware before deployment
+- Document all configuration options
+- Maintain backward compatibility where possible
+
+---
+
+**Last Updated**: 2025-11-22
+**Status**: Living document - update as improvements are implemented
diff --git a/deploy.sh b/deploy.sh
new file mode 100755
index 0000000..72c962f
--- /dev/null
+++ b/deploy.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+# deploy.sh - Git-based deployment to Unraid server
+
+set -e
+
+# Configuration
+UNRAID_HOST="${UNRAID_HOST:-root@192.168.2.61}"
+REMOTE_PATH="/boot/config/plugins/compose.manager/projects/unraid-dash"
+GIT_REPO="git@git.michaelsimard.ca:msimard/unraid-dashboard.git"
+
+echo "Deploying unraid-dash to ${UNRAID_HOST}..."
+
+ssh ${UNRAID_HOST} bash << EOF
+set -e
+
+# Ensure SSH config for git server (port 28)
+mkdir -p ~/.ssh
+chmod 700 ~/.ssh
+if ! grep -q "^Host git.michaelsimard.ca" ~/.ssh/config 2>/dev/null; then
+ echo "Configuring SSH for git.michaelsimard.ca (port 28)..."
+ cat >> ~/.ssh/config << 'SSHCONFIG'
+
+Host git.michaelsimard.ca
+ Port 28
+SSHCONFIG
+ chmod 600 ~/.ssh/config
+fi
+
+# Clone or pull repository
+if [ -d "${REMOTE_PATH}/.git" ]; then
+ echo "Repository exists, pulling latest changes..."
+ cd ${REMOTE_PATH}
+ git pull
+else
+ echo "Cloning repository..."
+ git clone ${GIT_REPO} ${REMOTE_PATH}
+ cd ${REMOTE_PATH}
+fi
+
+# Fix file permissions for Docker build (required for /boot partition)
+echo "Fixing file permissions..."
+chmod -R 755 ${REMOTE_PATH}
+chmod 644 ${REMOTE_PATH}/*.html ${REMOTE_PATH}/*.css ${REMOTE_PATH}/*.js ${REMOTE_PATH}/*.yml ${REMOTE_PATH}/*.md 2>/dev/null || true
+
+# Check if config.js exists
+if [ ! -f config.js ]; then
+ echo "WARNING: config.js file not found!"
+ echo "Please create ${REMOTE_PATH}/config.js with required configuration"
+ echo "You can copy from config.example.js and modify as needed"
+ exit 1
+fi
+
+# Build and deploy
+echo "Building and starting container..."
+docker compose up -d --build
+
+# Show status
+echo ""
+echo "Deployment complete!"
+docker compose ps
+echo ""
+echo "View logs with: ssh ${UNRAID_HOST} 'cd ${REMOTE_PATH} && docker compose logs -f'"
+EOF
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..351363a
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,12 @@
+services:
+ unraid-dash:
+ build: .
+ container_name: unraid-dash
+ restart: unless-stopped
+ ports:
+ - "9113:80"
+ logging:
+ driver: "json-file"
+ options:
+ max-size: "10m"
+ max-file: "3"
diff --git a/index.html b/index.html
index d25de46..12dc77e 100644
--- a/index.html
+++ b/index.html
@@ -7,6 +7,7 @@
+
THANOS SYSTEMS MONITOR
@@ -19,7 +20,7 @@