From 9a23c1c05b9e1a174a2941b10e5a3283351221e6 Mon Sep 17 00:00:00 2001 From: stephenminakian Date: Wed, 2 Jul 2025 16:01:38 -0600 Subject: [PATCH] Updated lint errors --- .eslintrc.json | 98 +++++--- package.json | 94 +++---- src/app.js | 6 +- src/middleware/logger.js | 14 +- src/public/app.js | 516 +++++++++++++++++++-------------------- src/public/index.html | 141 +++++------ src/public/style.css | 344 +++++++++++++------------- src/routes/api.js | 8 +- src/routes/health.js | 14 +- src/utils/metrics.js | 168 ++++++------- 10 files changed, 731 insertions(+), 672 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 65529ee..7d1bc4e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,34 +1,68 @@ { - "env": { - "browser": true, - "commonjs": true, - "es2021": true, - "node": true, - "jest": true - }, - "extends": [ - "eslint:recommended" + "env": { + "browser": true, + "commonjs": true, + "es2021": true, + "node": true, + "jest": true + }, + "extends": [ + "eslint:recommended" + ], + "parserOptions": { + "ecmaVersion": "latest" + }, + "rules": { + "indent": [ + "error", + 2 ], - "parserOptions": { - "ecmaVersion": "latest" - }, - "rules": { - "indent": ["error", 2], - "linebreak-style": ["error", "unix"], - "quotes": ["error", "single"], - "semi": ["error", "always"], - "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], - "no-console": "off", - "no-trailing-spaces": "error", - "eol-last": "error", - "comma-dangle": ["error", "never"], - "object-curly-spacing": ["error", "always"], - "array-bracket-spacing": ["error", "never"], - "space-before-function-paren": ["error", "never"], - "keyword-spacing": "error", - "space-infix-ops": "error", - "no-multiple-empty-lines": ["error", { "max": 2 }], - "prefer-const": "error", - "no-var": "error" - } - } \ No newline at end of file + "linebreak-style": [ + "error", + "unix" + ], + "quotes": [ + "error", + "single" + ], + "semi": [ + "error", + "always" + ], + "no-unused-vars": [ + "error", + { + "argsIgnorePattern": "^_" + } + ], + "no-console": "off", + "no-trailing-spaces": "error", + "eol-last": "error", + "comma-dangle": [ + "error", + "never" + ], + "object-curly-spacing": [ + "error", + "always" + ], + "array-bracket-spacing": [ + "error", + "never" + ], + "space-before-function-paren": [ + "error", + "never" + ], + "keyword-spacing": "error", + "space-infix-ops": "error", + "no-multiple-empty-lines": [ + "error", + { + "max": 2 + } + ], + "prefer-const": "error", + "no-var": "error" + } +} \ No newline at end of file diff --git a/package.json b/package.json index b952e3d..f882d5b 100644 --- a/package.json +++ b/package.json @@ -1,47 +1,53 @@ { - "name": "harbor-ci-cd-demo", - "version": "1.0.0", - "description": "System Monitoring Dashboard - Harbor CI/CD Demo", - "main": "src/app.js", - "scripts": { - "start": "node src/app.js", - "dev": "nodemon src/app.js", - "test": "jest", - "test:watch": "jest --watch", - "test:coverage": "jest --coverage", - "lint": "eslint src/ tests/", - "lint:fix": "eslint src/ tests/ --fix" - }, - "keywords": ["monitoring", "dashboard", "nodejs", "cicd", "harbor"], - "author": "Your Name", - "license": "MIT", - "dependencies": { - "express": "^4.18.2", - "cors": "^2.8.5", - "helmet": "^7.1.0", - "morgan": "^1.10.0", - "compression": "^1.7.4", - "dotenv": "^16.3.1" - }, - "devDependencies": { - "jest": "^29.7.0", - "supertest": "^6.3.3", - "eslint": "^8.53.0", - "nodemon": "^3.0.1" - }, - "jest": { - "testEnvironment": "node", - "collectCoverageFrom": [ - "src/**/*.js", - "!src/public/**" - ], - "coverageThreshold": { - "global": { - "branches": 80, - "functions": 80, - "lines": 80, - "statements": 80 - } + "name": "harbor-ci-cd-demo", + "version": "1.0.0", + "description": "System Monitoring Dashboard - Harbor CI/CD Demo", + "main": "src/app.js", + "scripts": { + "start": "node src/app.js", + "dev": "nodemon src/app.js", + "test": "jest", + "test:watch": "jest --watch", + "test:coverage": "jest --coverage", + "lint": "eslint src/ tests/", + "lint:fix": "eslint src/ tests/ --fix" + }, + "keywords": [ + "monitoring", + "dashboard", + "nodejs", + "cicd", + "harbor" + ], + "author": "Your Name", + "license": "MIT", + "dependencies": { + "express": "^4.18.2", + "cors": "^2.8.5", + "helmet": "^7.1.0", + "morgan": "^1.10.0", + "compression": "^1.7.4", + "dotenv": "^16.3.1" + }, + "devDependencies": { + "jest": "^29.7.0", + "supertest": "^6.3.3", + "eslint": "^8.53.0", + "nodemon": "^3.0.1" + }, + "jest": { + "testEnvironment": "node", + "collectCoverageFrom": [ + "src/**/*.js", + "!src/public/**" + ], + "coverageThreshold": { + "global": { + "branches": 80, + "functions": 80, + "lines": 80, + "statements": 80 } } - } \ No newline at end of file + } +} \ No newline at end of file diff --git a/src/app.js b/src/app.js index 5123207..6ebb5aa 100644 --- a/src/app.js +++ b/src/app.js @@ -47,7 +47,7 @@ app.get('/debug/files', (req, res) => { url: `http://localhost:${PORT}/${file}` }; }); - + res.json({ publicPath, files: fileDetails, @@ -101,7 +101,7 @@ app.get('/test-static', (req, res) => { app.get('/', (req, res) => { const indexPath = path.join(publicPath, 'index.html'); console.log('๐Ÿ  Serving index.html from:', indexPath); - + if (require('fs').existsSync(indexPath)) { res.sendFile(indexPath); } else { @@ -119,7 +119,7 @@ app.use((err, req, res, next) => { // 404 handler app.use('*', (req, res) => { console.log('๐Ÿ” 404 for:', req.originalUrl); - res.status(404).json({ + res.status(404).json({ error: 'Not Found', url: req.originalUrl, message: 'Static file or route not found' diff --git a/src/middleware/logger.js b/src/middleware/logger.js index b1b8b7c..9d3b339 100644 --- a/src/middleware/logger.js +++ b/src/middleware/logger.js @@ -3,10 +3,10 @@ const { incrementCounter, recordHistogram } = require('../utils/metrics'); // Request logging and metrics middleware function logger(req, res, next) { const startTime = Date.now(); - + // Log request start console.log(`${new Date().toISOString()} - ${req.method} ${req.url} - ${req.ip}`); - + // Increment request counter incrementCounter('http_requests_total', { method: req.method, @@ -15,24 +15,24 @@ function logger(req, res, next) { // Override res.end to capture response time and status const originalEnd = res.end; - res.end = function(chunk, encoding) { + res.end = function (chunk, encoding) { const responseTime = Date.now() - startTime; - + // Record response time histogram recordHistogram('http_request_duration_ms', responseTime, { method: req.method, status_code: res.statusCode }); - + // Increment response counter incrementCounter('http_responses_total', { method: req.method, status_code: res.statusCode }); - + // Log request completion console.log(`${new Date().toISOString()} - ${req.method} ${req.url} - ${res.statusCode} - ${responseTime}ms`); - + // Call original end method originalEnd.call(this, chunk, encoding); }; diff --git a/src/public/app.js b/src/public/app.js index 4d09b7b..c6dcec5 100644 --- a/src/public/app.js +++ b/src/public/app.js @@ -1,113 +1,113 @@ class MonitoringDashboard { - constructor() { - this.refreshInterval = null; - this.autoRefresh = true; - this.refreshRate = 30000; // 30 seconds - this.init(); - } + constructor() { + this.refreshInterval = null; + this.autoRefresh = true; + this.refreshRate = 30000; // 30 seconds + this.init(); + } - async init() { - await this.loadAllData(); - this.startAutoRefresh(); - this.setupEventListeners(); - } + async init() { + await this.loadAllData(); + this.startAutoRefresh(); + this.setupEventListeners(); + } - async loadAllData() { - this.updateStatus('loading', 'Loading...'); - - try { - await Promise.all([ - this.loadSystemInfo(), - this.loadMemoryUsage(), - this.loadHealthStatus(), - this.loadApiMetrics(), - this.loadProcessInfo() - ]); - - this.updateStatus('healthy', 'System Healthy'); - this.updateLastUpdated(); - } catch (error) { - console.error('Error loading data:', error); - this.updateStatus('error', 'Error Loading Data'); - } - } + async loadAllData() { + this.updateStatus('loading', 'Loading...'); - async loadSystemInfo() { - try { - const response = await fetch('/api/system'); - const result = await response.json(); - - if (result.success) { - this.renderSystemInfo(result.data); - } else { - throw new Error(result.error); - } - } catch (error) { - this.renderError('system-info', 'Failed to load system information'); - } - } + try { + await Promise.all([ + this.loadSystemInfo(), + this.loadMemoryUsage(), + this.loadHealthStatus(), + this.loadApiMetrics(), + this.loadProcessInfo() + ]); - async loadMemoryUsage() { - try { - const response = await fetch('/api/memory'); - const result = await response.json(); - - if (result.success) { - this.renderMemoryUsage(result.data); - } else { - throw new Error(result.error); - } - } catch (error) { - this.renderError('memory-usage', 'Failed to load memory data'); - } + this.updateStatus('healthy', 'System Healthy'); + this.updateLastUpdated(); + } catch (error) { + console.error('Error loading data:', error); + this.updateStatus('error', 'Error Loading Data'); } + } - async loadHealthStatus() { - try { - const response = await fetch('/health/detailed'); - const result = await response.json(); - - this.renderHealthStatus(result); - this.updateOverallStatus(result.status); - } catch (error) { - this.renderError('health-status', 'Failed to load health data'); - this.updateStatus('error', 'Health Check Failed'); - } + async loadSystemInfo() { + try { + const response = await fetch('/api/system'); + const result = await response.json(); + + if (result.success) { + this.renderSystemInfo(result.data); + } else { + throw new Error(result.error); + } + } catch (error) { + this.renderError('system-info', 'Failed to load system information'); } + } - async loadApiMetrics() { - try { - const response = await fetch('/api/metrics'); - const result = await response.json(); - - if (result.success) { - this.renderApiMetrics(result.data); - } else { - throw new Error(result.error); - } - } catch (error) { - this.renderError('api-metrics', 'Failed to load metrics'); - } + async loadMemoryUsage() { + try { + const response = await fetch('/api/memory'); + const result = await response.json(); + + if (result.success) { + this.renderMemoryUsage(result.data); + } else { + throw new Error(result.error); + } + } catch (error) { + this.renderError('memory-usage', 'Failed to load memory data'); } + } - async loadProcessInfo() { - try { - const response = await fetch('/api/process'); - const result = await response.json(); - - if (result.success) { - this.renderProcessInfo(result.data); - } else { - throw new Error(result.error); - } - } catch (error) { - this.renderError('process-info', 'Failed to load process information'); - } + async loadHealthStatus() { + try { + const response = await fetch('/health/detailed'); + const result = await response.json(); + + this.renderHealthStatus(result); + this.updateOverallStatus(result.status); + } catch (error) { + this.renderError('health-status', 'Failed to load health data'); + this.updateStatus('error', 'Health Check Failed'); } + } - renderSystemInfo(data) { - const container = document.getElementById('system-info'); - container.innerHTML = ` + async loadApiMetrics() { + try { + const response = await fetch('/api/metrics'); + const result = await response.json(); + + if (result.success) { + this.renderApiMetrics(result.data); + } else { + throw new Error(result.error); + } + } catch (error) { + this.renderError('api-metrics', 'Failed to load metrics'); + } + } + + async loadProcessInfo() { + try { + const response = await fetch('/api/process'); + const result = await response.json(); + + if (result.success) { + this.renderProcessInfo(result.data); + } else { + throw new Error(result.error); + } + } catch (error) { + this.renderError('process-info', 'Failed to load process information'); + } + } + + renderSystemInfo(data) { + const container = document.getElementById('system-info'); + container.innerHTML = `
Hostname: ${data.hostname} @@ -137,18 +137,18 @@ class MonitoringDashboard { ${data.nodeVersion}
`; - } + } - renderMemoryUsage(data) { - const container = document.getElementById('memory-usage'); - - const systemPercentage = data.system.percentage; - const systemClass = systemPercentage > 80 ? 'danger' : systemPercentage > 60 ? 'warning' : ''; - - const processHeapPercentage = (data.process.heapUsed / data.process.heapTotal) * 100; - const processClass = processHeapPercentage > 80 ? 'danger' : processHeapPercentage > 60 ? 'warning' : ''; - - container.innerHTML = ` + renderMemoryUsage(data) { + const container = document.getElementById('memory-usage'); + + const systemPercentage = data.system.percentage; + const systemClass = systemPercentage > 80 ? 'danger' : systemPercentage > 60 ? 'warning' : ''; + + const processHeapPercentage = (data.process.heapUsed / data.process.heapTotal) * 100; + const processClass = processHeapPercentage > 80 ? 'danger' : processHeapPercentage > 60 ? 'warning' : ''; + + container.innerHTML = `
System Memory @@ -180,15 +180,15 @@ class MonitoringDashboard { ${data.process.external} MB
`; - } + } - renderHealthStatus(data) { - const container = document.getElementById('health-status'); - - const statusClass = data.status === 'healthy' ? 'metric-item' : - data.status === 'warning' ? 'metric-item warning' : 'metric-item error'; - - container.innerHTML = ` + renderHealthStatus(data) { + const container = document.getElementById('health-status'); + + const statusClass = data.status === 'healthy' ? 'metric-item' : + data.status === 'warning' ? 'metric-item warning' : 'metric-item error'; + + container.innerHTML = `
Overall Status: ${data.status.toUpperCase()} @@ -214,61 +214,61 @@ class MonitoringDashboard { ${data.loadAverage.join(', ')}
`; - } + } - renderApiMetrics(data) { - const container = document.getElementById('api-metrics'); - - const requestCounters = data.counters.filter(c => c.name === 'http_requests_total'); - const responseCounters = data.counters.filter(c => c.name === 'http_responses_total'); - const durationHistograms = data.histograms.filter(h => h.name === 'http_request_duration_ms'); - - let html = '
'; - - // Total requests - const totalRequests = requestCounters.reduce((sum, counter) => sum + counter.value, 0); - html += ` + renderApiMetrics(data) { + const container = document.getElementById('api-metrics'); + + const requestCounters = data.counters.filter(c => c.name === 'http_requests_total'); + const responseCounters = data.counters.filter(c => c.name === 'http_responses_total'); + const durationHistograms = data.histograms.filter(h => h.name === 'http_request_duration_ms'); + + let html = '
'; + + // Total requests + const totalRequests = requestCounters.reduce((sum, counter) => sum + counter.value, 0); + html += `
Total Requests: ${totalRequests}
`; - - // Average response time - const avgResponseTime = durationHistograms.length > 0 ? - durationHistograms.reduce((sum, hist) => sum + hist.average, 0) / durationHistograms.length : 0; - html += ` + + // Average response time + const avgResponseTime = durationHistograms.length > 0 ? + durationHistograms.reduce((sum, hist) => sum + hist.average, 0) / durationHistograms.length : 0; + html += `
Avg Response Time: ${Math.round(avgResponseTime)}ms
`; - - // Uptime - html += ` + + // Uptime + html += `
Service Uptime: ${this.formatUptime(data.runtime.uptime_seconds)}
`; - - // API endpoints - const apiCounters = requestCounters.filter(c => c.labels.route && c.labels.route.startsWith('/api')); - if (apiCounters.length > 0) { - html += '
API Endpoints:
'; - apiCounters.forEach(counter => { - html += `
${counter.labels.route}: ${counter.value} calls
`; - }); - html += '
'; - } - - html += '
'; - container.innerHTML = html; + + // API endpoints + const apiCounters = requestCounters.filter(c => c.labels.route && c.labels.route.startsWith('/api')); + if (apiCounters.length > 0) { + html += '
API Endpoints:
'; + apiCounters.forEach(counter => { + html += `
${counter.labels.route}: ${counter.value} calls
`; + }); + html += '
'; } - renderProcessInfo(data) { - const container = document.getElementById('process-info'); - container.innerHTML = ` + html += '
'; + container.innerHTML = html; + } + + renderProcessInfo(data) { + const container = document.getElementById('process-info'); + container.innerHTML = `
Process ID: ${data.pid} @@ -294,129 +294,129 @@ class MonitoringDashboard { ${data.env}
`; + } + + renderError(containerId, message) { + const container = document.getElementById(containerId); + container.innerHTML = `
${message}
`; + } + + updateStatus(status, text) { + const indicator = document.getElementById('status-indicator'); + const dot = document.getElementById('status-dot'); + const statusText = document.getElementById('status-text'); + + indicator.className = `status-indicator ${status}`; + statusText.textContent = text; + } + + updateOverallStatus(healthStatus) { + if (healthStatus === 'healthy') { + this.updateStatus('healthy', 'System Healthy'); + } else if (healthStatus === 'warning') { + this.updateStatus('warning', 'System Warning'); + } else { + this.updateStatus('error', 'System Error'); + } + } + + updateLastUpdated() { + const element = document.getElementById('last-updated'); + element.textContent = new Date().toLocaleString(); + } + + formatUptime(seconds) { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + const secs = Math.floor(seconds % 60); + + if (days > 0) { + return `${days}d ${hours}h ${minutes}m`; + } else if (hours > 0) { + return `${hours}h ${minutes}m ${secs}s`; + } else if (minutes > 0) { + return `${minutes}m ${secs}s`; + } else { + return `${secs}s`; + } + } + + startAutoRefresh() { + if (this.refreshInterval) { + clearInterval(this.refreshInterval); } - renderError(containerId, message) { - const container = document.getElementById(containerId); - container.innerHTML = `
${message}
`; + if (this.autoRefresh) { + this.refreshInterval = setInterval(() => { + this.loadAllData(); + }, this.refreshRate); } + } - updateStatus(status, text) { - const indicator = document.getElementById('status-indicator'); - const dot = document.getElementById('status-dot'); - const statusText = document.getElementById('status-text'); - - indicator.className = `status-indicator ${status}`; - statusText.textContent = text; - } - - updateOverallStatus(healthStatus) { - if (healthStatus === 'healthy') { - this.updateStatus('healthy', 'System Healthy'); - } else if (healthStatus === 'warning') { - this.updateStatus('warning', 'System Warning'); - } else { - this.updateStatus('error', 'System Error'); - } - } - - updateLastUpdated() { - const element = document.getElementById('last-updated'); - element.textContent = new Date().toLocaleString(); - } - - formatUptime(seconds) { - const days = Math.floor(seconds / 86400); - const hours = Math.floor((seconds % 86400) / 3600); - const minutes = Math.floor((seconds % 3600) / 60); - const secs = Math.floor(seconds % 60); - - if (days > 0) { - return `${days}d ${hours}h ${minutes}m`; - } else if (hours > 0) { - return `${hours}h ${minutes}m ${secs}s`; - } else if (minutes > 0) { - return `${minutes}m ${secs}s`; - } else { - return `${secs}s`; - } - } - - startAutoRefresh() { - if (this.refreshInterval) { - clearInterval(this.refreshInterval); - } - - if (this.autoRefresh) { - this.refreshInterval = setInterval(() => { - this.loadAllData(); - }, this.refreshRate); - } - } - - setupEventListeners() { - // Auto-refresh toggle (could add a button for this) - document.addEventListener('visibilitychange', () => { - if (document.hidden) { - clearInterval(this.refreshInterval); - } else { - this.startAutoRefresh(); - } - }); - } + setupEventListeners() { + // Auto-refresh toggle (could add a button for this) + document.addEventListener('visibilitychange', () => { + if (document.hidden) { + clearInterval(this.refreshInterval); + } else { + this.startAutoRefresh(); + } + }); + } } // Global functions for buttons async function refreshData() { - const dashboard = window.dashboard; - if (dashboard) { - await dashboard.loadAllData(); - } + const dashboard = window.dashboard; + if (dashboard) { + await dashboard.loadAllData(); + } } async function testAPI() { - try { - const response = await fetch('/api/test'); - const result = await response.json(); - - if (result.success) { - alert('โœ… API Test Successful!\n\n' + - `Message: ${result.message}\n` + - `Version: ${result.version}\n` + - `Environment: ${result.environment}`); - } else { - alert('โŒ API Test Failed'); - } - } catch (error) { - alert('โŒ API Test Failed: ' + error.message); + try { + const response = await fetch('/api/test'); + const result = await response.json(); + + if (result.success) { + alert('โœ… API Test Successful!\n\n' + + `Message: ${result.message}\n` + + `Version: ${result.version}\n` + + `Environment: ${result.environment}`); + } else { + alert('โŒ API Test Failed'); } + } catch (error) { + alert('โŒ API Test Failed: ' + error.message); + } } async function downloadMetrics() { - try { - const response = await fetch('/api/metrics'); - const result = await response.json(); - - if (result.success) { - const dataStr = JSON.stringify(result.data, null, 2); - const dataBlob = new Blob([dataStr], {type: 'application/json'}); - const url = URL.createObjectURL(dataBlob); - - const link = document.createElement('a'); - link.href = url; - link.download = `metrics-${new Date().toISOString().split('T')[0]}.json`; - link.click(); - - URL.revokeObjectURL(url); - } else { - alert('Failed to download metrics'); - } - } catch (error) { - alert('Error downloading metrics: ' + error.message); + try { + const response = await fetch('/api/metrics'); + const result = await response.json(); + + if (result.success) { + const dataStr = JSON.stringify(result.data, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + + const link = document.createElement('a'); + link.href = url; + link.download = `metrics-${new Date().toISOString().split('T')[0]}.json`; + link.click(); + + URL.revokeObjectURL(url); + } else { + alert('Failed to download metrics'); } + } catch (error) { + alert('Error downloading metrics: ' + error.message); + } } // Initialize dashboard when page loads document.addEventListener('DOMContentLoaded', () => { - window.dashboard = new MonitoringDashboard(); + window.dashboard = new MonitoringDashboard(); }); \ No newline at end of file diff --git a/src/public/index.html b/src/public/index.html index ba42c22..8b2bc13 100644 --- a/src/public/index.html +++ b/src/public/index.html @@ -1,81 +1,84 @@ + - - - System Monitoring Dashboard - + + + System Monitoring Dashboard + + -
-

๐Ÿ–ฅ๏ธ System Monitoring Dashboard

-

Real-time system metrics and health monitoring

-
- - Checking... +
+

๐Ÿ–ฅ๏ธ System Monitoring Dashboard

+

Real-time system metrics and health monitoring

+
+ + Checking... +
+
+ +
+
+ +
+

๐Ÿ“Š System Information

+
+
Loading system information...
-
+
-
-
- -
-

๐Ÿ“Š System Information

-
-
Loading system information...
-
-
- - -
-

๐Ÿ’พ Memory Usage

-
-
Loading memory data...
-
-
- - -
-

โค๏ธ Health Status

-
-
Loading health data...
-
-
- - -
-

๐Ÿ“ก API Metrics

-
-
Loading metrics...
-
-
- - -
-

โš™๏ธ Process Information

-
-
Loading process data...
-
-
- - -
-

๐Ÿš€ Quick Actions

-
- - - - ๐Ÿ” Detailed Health -
-
+ +
+

๐Ÿ’พ Memory Usage

+
+
Loading memory data...
-
+ - + +
+

โค๏ธ Health Status

+
+
Loading health data...
+
+
- + +
+

๐Ÿ“ก API Metrics

+
+
Loading metrics...
+
+
+ + +
+

โš™๏ธ Process Information

+
+
Loading process data...
+
+
+ + +
+

๐Ÿš€ Quick Actions

+
+ + + + ๐Ÿ” Detailed Health +
+
+ + + + + + + \ No newline at end of file diff --git a/src/public/style.css b/src/public/style.css index 85735d7..c29b5c0 100644 --- a/src/public/style.css +++ b/src/public/style.css @@ -1,276 +1,292 @@ * { - margin: 0; - padding: 0; - box-sizing: border-box; + margin: 0; + padding: 0; + box-sizing: border-box; } body { - font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: #333; - min-height: 100vh; - line-height: 1.6; + font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: #333; + min-height: 100vh; + line-height: 1.6; } header { - background: rgba(255, 255, 255, 0.95); - backdrop-filter: blur(10px); - padding: 2rem; - text-align: center; - box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); - margin-bottom: 2rem; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + padding: 2rem; + text-align: center; + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); + margin-bottom: 2rem; } header h1 { - color: #4a5568; - font-size: 2.5rem; - margin-bottom: 0.5rem; + color: #4a5568; + font-size: 2.5rem; + margin-bottom: 0.5rem; } header p { - color: #718096; - font-size: 1.1rem; - margin-bottom: 1rem; + color: #718096; + font-size: 1.1rem; + margin-bottom: 1rem; } .status-indicator { - display: inline-flex; - align-items: center; - gap: 0.5rem; - background: rgba(16, 185, 129, 0.1); - padding: 0.5rem 1rem; - border-radius: 25px; - border: 2px solid #10b981; + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: rgba(16, 185, 129, 0.1); + padding: 0.5rem 1rem; + border-radius: 25px; + border: 2px solid #10b981; } .status-dot { - width: 12px; - height: 12px; - border-radius: 50%; - background: #10b981; - animation: pulse 2s infinite; + width: 12px; + height: 12px; + border-radius: 50%; + background: #10b981; + animation: pulse 2s infinite; } .status-indicator.warning { - background: rgba(245, 158, 11, 0.1); - border-color: #f59e0b; + background: rgba(245, 158, 11, 0.1); + border-color: #f59e0b; } .status-indicator.warning .status-dot { - background: #f59e0b; + background: #f59e0b; } .status-indicator.error { - background: rgba(239, 68, 68, 0.1); - border-color: #ef4444; + background: rgba(239, 68, 68, 0.1); + border-color: #ef4444; } .status-indicator.error .status-dot { - background: #ef4444; + background: #ef4444; } @keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.5; } + + 0%, + 100% { + opacity: 1; + } + + 50% { + opacity: 0.5; + } } main { - max-width: 1400px; - margin: 0 auto; - padding: 0 2rem 2rem; + max-width: 1400px; + margin: 0 auto; + padding: 0 2rem 2rem; } .dashboard-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); - gap: 2rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 2rem; } .card { - background: rgba(255, 255, 255, 0.95); - backdrop-filter: blur(10px); - border-radius: 16px; - padding: 2rem; - box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); - border: 1px solid rgba(255, 255, 255, 0.2); - transition: transform 0.3s ease, box-shadow 0.3s ease; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-radius: 16px; + padding: 2rem; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + border: 1px solid rgba(255, 255, 255, 0.2); + transition: transform 0.3s ease, box-shadow 0.3s ease; } .card:hover { - transform: translateY(-5px); - box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); + transform: translateY(-5px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); } .card h2 { - color: #4a5568; - font-size: 1.5rem; - margin-bottom: 1.5rem; - border-bottom: 2px solid #e2e8f0; - padding-bottom: 0.5rem; + color: #4a5568; + font-size: 1.5rem; + margin-bottom: 1.5rem; + border-bottom: 2px solid #e2e8f0; + padding-bottom: 0.5rem; } -.metric-grid, .health-grid, .process-grid, .metrics-grid { - display: grid; - gap: 1rem; +.metric-grid, +.health-grid, +.process-grid, +.metrics-grid { + display: grid; + gap: 1rem; } -.metric-item, .health-item, .process-item { - display: flex; - justify-content: space-between; - align-items: center; - padding: 0.75rem; - background: rgba(247, 250, 252, 0.8); - border-radius: 8px; - border-left: 4px solid #3b82f6; +.metric-item, +.health-item, +.process-item { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem; + background: rgba(247, 250, 252, 0.8); + border-radius: 8px; + border-left: 4px solid #3b82f6; } -.metric-item strong, .health-item strong, .process-item strong { - color: #374151; - font-weight: 600; +.metric-item strong, +.health-item strong, +.process-item strong { + color: #374151; + font-weight: 600; } -.metric-value, .health-value, .process-value { - color: #6b7280; - font-family: 'Courier New', monospace; - font-weight: 500; +.metric-value, +.health-value, +.process-value { + color: #6b7280; + font-family: 'Courier New', monospace; + font-weight: 500; } .memory-bars { - display: flex; - flex-direction: column; - gap: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; } .memory-bar { - background: #e5e7eb; - border-radius: 10px; - overflow: hidden; - height: 30px; - position: relative; + background: #e5e7eb; + border-radius: 10px; + overflow: hidden; + height: 30px; + position: relative; } .memory-bar-fill { - height: 100%; - background: linear-gradient(90deg, #10b981, #059669); - border-radius: 10px; - transition: width 0.5s ease; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-weight: 600; - font-size: 0.9rem; + height: 100%; + background: linear-gradient(90deg, #10b981, #059669); + border-radius: 10px; + transition: width 0.5s ease; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-weight: 600; + font-size: 0.9rem; } .memory-bar-fill.warning { - background: linear-gradient(90deg, #f59e0b, #d97706); + background: linear-gradient(90deg, #f59e0b, #d97706); } .memory-bar-fill.danger { - background: linear-gradient(90deg, #ef4444, #dc2626); + background: linear-gradient(90deg, #ef4444, #dc2626); } .memory-label { - display: flex; - justify-content: space-between; - margin-bottom: 0.5rem; - font-weight: 600; - color: #374151; + display: flex; + justify-content: space-between; + margin-bottom: 0.5rem; + font-weight: 600; + color: #374151; } .actions-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); - gap: 1rem; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 1rem; } .btn { - padding: 0.75rem 1rem; - border: none; - border-radius: 8px; - font-weight: 600; - text-decoration: none; - text-align: center; - cursor: pointer; - transition: all 0.3s ease; - display: inline-block; + padding: 0.75rem 1rem; + border: none; + border-radius: 8px; + font-weight: 600; + text-decoration: none; + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + display: inline-block; } .btn-primary { - background: linear-gradient(135deg, #3b82f6, #2563eb); - color: white; + background: linear-gradient(135deg, #3b82f6, #2563eb); + color: white; } .btn-primary:hover { - background: linear-gradient(135deg, #2563eb, #1d4ed8); - transform: translateY(-2px); + background: linear-gradient(135deg, #2563eb, #1d4ed8); + transform: translateY(-2px); } .btn-secondary { - background: linear-gradient(135deg, #6b7280, #4b5563); - color: white; + background: linear-gradient(135deg, #6b7280, #4b5563); + color: white; } .btn-secondary:hover { - background: linear-gradient(135deg, #4b5563, #374151); - transform: translateY(-2px); + background: linear-gradient(135deg, #4b5563, #374151); + transform: translateY(-2px); } .btn-info { - background: linear-gradient(135deg, #10b981, #059669); - color: white; + background: linear-gradient(135deg, #10b981, #059669); + color: white; } .btn-info:hover { - background: linear-gradient(135deg, #059669, #047857); - transform: translateY(-2px); + background: linear-gradient(135deg, #059669, #047857); + transform: translateY(-2px); } .loading { - text-align: center; - color: #9ca3af; - font-style: italic; - padding: 2rem; + text-align: center; + color: #9ca3af; + font-style: italic; + padding: 2rem; } .error { - background: rgba(239, 68, 68, 0.1); - color: #dc2626; - padding: 1rem; - border-radius: 8px; - border-left: 4px solid #ef4444; + background: rgba(239, 68, 68, 0.1); + color: #dc2626; + padding: 1rem; + border-radius: 8px; + border-left: 4px solid #ef4444; } footer { - text-align: center; - padding: 2rem; - color: rgba(255, 255, 255, 0.8); - background: rgba(0, 0, 0, 0.1); - margin-top: 2rem; + text-align: center; + padding: 2rem; + color: rgba(255, 255, 255, 0.8); + background: rgba(0, 0, 0, 0.1); + margin-top: 2rem; } @media (max-width: 768px) { - .dashboard-grid { - grid-template-columns: 1fr; - } - - header { - padding: 1rem; - } - - header h1 { - font-size: 2rem; - } - - main { - padding: 0 1rem 1rem; - } - - .card { - padding: 1.5rem; - } - - .actions-grid { - grid-template-columns: 1fr; - } + .dashboard-grid { + grid-template-columns: 1fr; + } + + header { + padding: 1rem; + } + + header h1 { + font-size: 2rem; + } + + main { + padding: 0 1rem 1rem; + } + + .card { + padding: 1.5rem; + } + + .actions-grid { + grid-template-columns: 1fr; + } } \ No newline at end of file diff --git a/src/routes/api.js b/src/routes/api.js index 6c5406e..9050081 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -8,7 +8,7 @@ const router = express.Router(); router.get('/system', (req, res) => { try { incrementCounter('api_calls_total', { endpoint: '/system' }); - + const systemInfo = { hostname: os.hostname(), platform: os.platform(), @@ -40,7 +40,7 @@ router.get('/system', (req, res) => { router.get('/memory', (req, res) => { try { incrementCounter('api_calls_total', { endpoint: '/memory' }); - + const memoryUsage = process.memoryUsage(); const totalMem = os.totalmem(); const freeMem = os.freemem(); @@ -79,7 +79,7 @@ router.get('/memory', (req, res) => { router.get('/process', (req, res) => { try { incrementCounter('api_calls_total', { endpoint: '/process' }); - + const processInfo = { pid: process.pid, ppid: process.ppid, @@ -126,7 +126,7 @@ router.get('/metrics', (req, res) => { // Test endpoint for CI/CD validation router.get('/test', (req, res) => { incrementCounter('api_calls_total', { endpoint: '/test' }); - + res.json({ success: true, message: 'API is working correctly', diff --git a/src/routes/health.js b/src/routes/health.js index 8ddca6c..1032326 100644 --- a/src/routes/health.js +++ b/src/routes/health.js @@ -18,18 +18,18 @@ router.get('/detailed', (req, res) => { const memoryUsage = process.memoryUsage(); const totalMem = os.totalmem(); const freeMem = os.freemem(); - + // Check memory usage (warn if over 80%) const memoryPercentage = ((totalMem - freeMem) / totalMem) * 100; const memoryStatus = memoryPercentage > 80 ? 'warning' : 'healthy'; - + // Check process memory (warn if heap over 100MB) const heapUsedMB = memoryUsage.heapUsed / 1024 / 1024; const processMemoryStatus = heapUsedMB > 100 ? 'warning' : 'healthy'; - + // Overall status - const overallStatus = (memoryStatus === 'warning' || processMemoryStatus === 'warning') - ? 'warning' + const overallStatus = (memoryStatus === 'warning' || processMemoryStatus === 'warning') + ? 'warning' : 'healthy'; res.json({ @@ -64,9 +64,9 @@ router.get('/detailed', (req, res) => { router.get('/ready', (req, res) => { // Check if the application is ready to serve traffic // In a real app, you might check database connections, external services, etc. - + const isReady = true; // Simplified check - + if (isReady) { res.json({ status: 'ready', diff --git a/src/utils/metrics.js b/src/utils/metrics.js index d850266..1140bf0 100644 --- a/src/utils/metrics.js +++ b/src/utils/metrics.js @@ -2,94 +2,94 @@ // In production, you'd use Prometheus, StatsD, or similar const metrics = { - counters: {}, - gauges: {}, - histograms: {}, - startTime: Date.now() - }; - - // Increment a counter - function incrementCounter(name, labels = {}) { - const key = `${name}_${JSON.stringify(labels)}`; - if (!metrics.counters[key]) { - metrics.counters[key] = { - name, - labels, - value: 0 - }; - } - metrics.counters[key].value++; - } - - // Set a gauge value - function setGauge(name, value, labels = {}) { - const key = `${name}_${JSON.stringify(labels)}`; - metrics.gauges[key] = { + counters: {}, + gauges: {}, + histograms: {}, + startTime: Date.now() +}; + +// Increment a counter +function incrementCounter(name, labels = {}) { + const key = `${name}_${JSON.stringify(labels)}`; + if (!metrics.counters[key]) { + metrics.counters[key] = { name, labels, - value, - timestamp: Date.now() + value: 0 }; } - - // Record histogram value (simplified) - function recordHistogram(name, value, labels = {}) { - const key = `${name}_${JSON.stringify(labels)}`; - if (!metrics.histograms[key]) { - metrics.histograms[key] = { - name, - labels, - values: [], - count: 0, - sum: 0 - }; - } - - const histogram = metrics.histograms[key]; - histogram.values.push(value); - histogram.count++; - histogram.sum += value; - - // Keep only last 1000 values to prevent memory issues - if (histogram.values.length > 1000) { - histogram.values = histogram.values.slice(-1000); - } - } - - // Get all metrics - function getMetrics() { - const runtime = { - uptime_seconds: Math.round((Date.now() - metrics.startTime) / 1000), - memory_usage_bytes: process.memoryUsage(), - cpu_usage_percent: process.cpuUsage() - }; - - return { - counters: Object.values(metrics.counters), - gauges: Object.values(metrics.gauges), - histograms: Object.values(metrics.histograms).map(h => ({ - ...h, - average: h.count > 0 ? h.sum / h.count : 0, - min: h.values.length > 0 ? Math.min(...h.values) : 0, - max: h.values.length > 0 ? Math.max(...h.values) : 0 - })), - runtime, - timestamp: new Date().toISOString() + metrics.counters[key].value++; +} + +// Set a gauge value +function setGauge(name, value, labels = {}) { + const key = `${name}_${JSON.stringify(labels)}`; + metrics.gauges[key] = { + name, + labels, + value, + timestamp: Date.now() + }; +} + +// Record histogram value (simplified) +function recordHistogram(name, value, labels = {}) { + const key = `${name}_${JSON.stringify(labels)}`; + if (!metrics.histograms[key]) { + metrics.histograms[key] = { + name, + labels, + values: [], + count: 0, + sum: 0 }; } - - // Reset all metrics - function resetMetrics() { - metrics.counters = {}; - metrics.gauges = {}; - metrics.histograms = {}; - metrics.startTime = Date.now(); + + const histogram = metrics.histograms[key]; + histogram.values.push(value); + histogram.count++; + histogram.sum += value; + + // Keep only last 1000 values to prevent memory issues + if (histogram.values.length > 1000) { + histogram.values = histogram.values.slice(-1000); } - - module.exports = { - incrementCounter, - setGauge, - recordHistogram, - getMetrics, - resetMetrics - }; \ No newline at end of file +} + +// Get all metrics +function getMetrics() { + const runtime = { + uptime_seconds: Math.round((Date.now() - metrics.startTime) / 1000), + memory_usage_bytes: process.memoryUsage(), + cpu_usage_percent: process.cpuUsage() + }; + + return { + counters: Object.values(metrics.counters), + gauges: Object.values(metrics.gauges), + histograms: Object.values(metrics.histograms).map(h => ({ + ...h, + average: h.count > 0 ? h.sum / h.count : 0, + min: h.values.length > 0 ? Math.min(...h.values) : 0, + max: h.values.length > 0 ? Math.max(...h.values) : 0 + })), + runtime, + timestamp: new Date().toISOString() + }; +} + +// Reset all metrics +function resetMetrics() { + metrics.counters = {}; + metrics.gauges = {}; + metrics.histograms = {}; + metrics.startTime = Date.now(); +} + +module.exports = { + incrementCounter, + setGauge, + recordHistogram, + getMetrics, + resetMetrics +}; \ No newline at end of file