feat: add capacity planning docs, image upload system, and platform health monitoring
Documentation: - Add comprehensive capacity planning guide (docs/architecture/capacity-planning.md) - Add operations docs: platform-health, capacity-monitoring, image-storage - Link pricing strategy to capacity planning documentation - Update mkdocs.yml with new Operations section Image Upload System: - Add ImageService with WebP conversion and sharded directory structure - Generate multiple size variants (original, 800px, 200px) - Add storage stats endpoint for monitoring - Add Pillow dependency for image processing Platform Health Monitoring: - Add /admin/platform-health page with real-time metrics - Show CPU, memory, disk usage with progress bars - Display capacity thresholds with status indicators - Generate scaling recommendations automatically - Determine infrastructure tier based on usage - Add psutil dependency for system metrics Admin UI: - Add Capacity Monitor to Platform Health section in sidebar - Create platform-health.html template with stats cards - Create platform-health.js for Alpine.js state management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
128
static/admin/js/platform-health.js
Normal file
128
static/admin/js/platform-health.js
Normal file
@@ -0,0 +1,128 @@
|
||||
// static/admin/js/platform-health.js
|
||||
/**
|
||||
* Admin platform health monitoring page logic
|
||||
* Displays system metrics, capacity thresholds, and scaling recommendations
|
||||
*/
|
||||
|
||||
const adminPlatformHealthLog = window.LogConfig.loggers.adminPlatformHealth ||
|
||||
window.LogConfig.createLogger('adminPlatformHealth', false);
|
||||
|
||||
adminPlatformHealthLog.info('Loading...');
|
||||
|
||||
function adminPlatformHealth() {
|
||||
adminPlatformHealthLog.info('adminPlatformHealth() called');
|
||||
|
||||
return {
|
||||
// Inherit base layout state
|
||||
...data(),
|
||||
|
||||
// Set page identifier
|
||||
currentPage: 'platform-health',
|
||||
|
||||
// Loading states
|
||||
loading: true,
|
||||
error: '',
|
||||
|
||||
// Health data
|
||||
health: null,
|
||||
|
||||
// Auto-refresh interval (30 seconds)
|
||||
refreshInterval: null,
|
||||
|
||||
async init() {
|
||||
adminPlatformHealthLog.info('Platform Health init() called');
|
||||
|
||||
// Guard against multiple initialization
|
||||
if (window._adminPlatformHealthInitialized) {
|
||||
adminPlatformHealthLog.warn('Already initialized, skipping');
|
||||
return;
|
||||
}
|
||||
window._adminPlatformHealthInitialized = true;
|
||||
|
||||
// Load initial data
|
||||
await this.loadHealth();
|
||||
|
||||
// Set up auto-refresh every 30 seconds
|
||||
this.refreshInterval = setInterval(() => {
|
||||
this.loadHealth();
|
||||
}, 30000);
|
||||
|
||||
adminPlatformHealthLog.info('Platform Health initialization complete');
|
||||
},
|
||||
|
||||
/**
|
||||
* Clean up on component destroy
|
||||
*/
|
||||
destroy() {
|
||||
if (this.refreshInterval) {
|
||||
clearInterval(this.refreshInterval);
|
||||
this.refreshInterval = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Load platform health data
|
||||
*/
|
||||
async loadHealth() {
|
||||
this.loading = true;
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
const response = await apiClient.get('/admin/platform/health');
|
||||
this.health = response;
|
||||
|
||||
adminPlatformHealthLog.info('Loaded health data:', {
|
||||
status: response.overall_status,
|
||||
tier: response.infrastructure_tier
|
||||
});
|
||||
} catch (error) {
|
||||
adminPlatformHealthLog.error('Failed to load health:', error);
|
||||
this.error = error.message || 'Failed to load platform health';
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Manual refresh
|
||||
*/
|
||||
async refresh() {
|
||||
await this.loadHealth();
|
||||
},
|
||||
|
||||
/**
|
||||
* Format number with locale
|
||||
*/
|
||||
formatNumber(num) {
|
||||
if (num === null || num === undefined) return '0';
|
||||
if (typeof num === 'number' && num % 1 !== 0) {
|
||||
return num.toFixed(2);
|
||||
}
|
||||
return new Intl.NumberFormat('en-US').format(num);
|
||||
},
|
||||
|
||||
/**
|
||||
* Format storage size
|
||||
*/
|
||||
formatStorage(gb) {
|
||||
if (gb === null || gb === undefined) return '0 GB';
|
||||
if (gb < 1) {
|
||||
return (gb * 1024).toFixed(0) + ' MB';
|
||||
}
|
||||
return gb.toFixed(2) + ' GB';
|
||||
},
|
||||
|
||||
/**
|
||||
* Format timestamp
|
||||
*/
|
||||
formatTime(timestamp) {
|
||||
if (!timestamp) return 'Unknown';
|
||||
try {
|
||||
const date = new Date(timestamp);
|
||||
return date.toLocaleTimeString();
|
||||
} catch (e) {
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user