UNPKG

@vasoyaprince14/sql-analyzer

Version:

🚀 Enhanced SQL database analyzer with AI-powered insights, comprehensive security analysis, RLS policy auditing, and beautiful HTML reports

671 lines (627 loc) 28.6 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.HealthReportGenerator = void 0; const chalk_1 = __importDefault(require("chalk")); class HealthReportGenerator { /** * Generate a comprehensive CLI health report */ generateCLIReport(report) { let output = ''; output += this.generateCLIHeader(); output += this.generateCLIDatabaseInfoSection(report); output += this.generateCLISchemaHealthSection(report); output += this.generateCLIIndexAnalysisSection(report); output += this.generateCLIPerformanceSection(report); output += this.generateCLISecuritySection(report); output += this.generateCLICostAnalysisSection(report); output += this.generateCLIRecommendationsSection(report); output += this.generateCLIMaintenanceSection(report); output += this.generateCLISummarySection(report); return output; } /** * Generate JSON report */ generateJSONReport(report) { return JSON.stringify(report, null, 2); } /** * Generate HTML report */ generateHTMLReport(report) { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Database Health Audit Report</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif; line-height: 1.6; color: #333; background: #f8f9fa; padding: 20px; } .container { max-width: 1200px; margin: 0 auto; background: white; border-radius: 12px; box-shadow: 0 4px 6px rgba(0,0,0,0.1); } .header { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 40px 30px; border-radius: 12px 12px 0 0; text-align: center; } .header h1 { font-size: 2.5em; margin-bottom: 10px; } .header p { opacity: 0.9; font-size: 1.1em; } .content { padding: 30px; } .section { margin-bottom: 40px; padding: 25px; border: 1px solid #e9ecef; border-radius: 8px; background: #fff; } .section h2 { color: #495057; margin-bottom: 20px; padding-bottom: 10px; border-bottom: 2px solid #e9ecef; font-size: 1.5em; } .score-container { text-align: center; padding: 20px; } .score { font-size: 4em; font-weight: bold; margin-bottom: 10px; text-shadow: 2px 2px 4px rgba(0,0,0,0.1); } .score.excellent { color: #28a745; } .score.good { color: #ffc107; } .score.poor { color: #dc3545; } .metrics-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0; } .metric-card { background: #f8f9fa; padding: 20px; border-radius: 8px; text-align: center; border-left: 4px solid #667eea; } .metric-value { font-size: 1.8em; font-weight: bold; color: #495057; } .metric-label { color: #6c757d; margin-top: 5px; } table { width: 100%; border-collapse: collapse; margin: 20px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1); border-radius: 8px; overflow: hidden; } th, td { padding: 12px 15px; text-align: left; border-bottom: 1px solid #e9ecef; } th { background: #495057; color: white; font-weight: 600; text-transform: uppercase; font-size: 0.85em; letter-spacing: 0.5px; } tr:hover { background: #f8f9fa; } .issue { margin: 15px 0; padding: 15px; border-radius: 8px; border-left: 4px solid; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .issue.critical { background: #f8d7da; border-left-color: #dc3545; color: #721c24; } .issue.high { background: #fff3cd; border-left-color: #ffc107; color: #856404; } .issue.medium { background: #d1ecf1; border-left-color: #17a2b8; color: #0c5460; } .issue.low { background: #d4edda; border-left-color: #28a745; color: #155724; } .severity-badge { display: inline-block; padding: 4px 8px; border-radius: 12px; font-size: 0.75em; font-weight: bold; text-transform: uppercase; margin-bottom: 8px; } .severity-critical { background: #dc3545; color: white; } .severity-high { background: #fd7e14; color: white; } .severity-medium { background: #ffc107; color: #000; } .severity-low { background: #28a745; color: white; } .recommendation { margin: 15px 0; padding: 20px; background: linear-gradient(45deg, #e7f3ff, #f0f8ff); border-radius: 8px; border-left: 4px solid #007bff; box-shadow: 0 2px 4px rgba(0,0,0,0.05); } .recommendation h4 { color: #0056b3; margin-bottom: 10px; font-size: 1.2em; } .recommendation p { margin: 8px 0; } .recommendation strong { color: #495057; } .sql-code { background: #2d3748; color: #e2e8f0; padding: 12px 15px; border-radius: 6px; font-family: 'Monaco', 'Consolas', monospace; font-size: 0.9em; margin: 10px 0; overflow-x: auto; white-space: pre-wrap; } .summary-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 15px; margin: 20px 0; } .stat-item { text-align: center; padding: 15px; background: #f8f9fa; border-radius: 8px; } .stat-number { font-size: 2em; font-weight: bold; color: #495057; } .stat-label { color: #6c757d; font-size: 0.9em; margin-top: 5px; } .index-list { max-height: 400px; overflow-y: auto; border: 1px solid #e9ecef; border-radius: 6px; padding: 15px; } .index-item { padding: 8px 12px; margin: 5px 0; background: #f8f9fa; border-radius: 4px; display: flex; justify-content: space-between; align-items: center; } .index-name { font-weight: 600; color: #495057; } .index-size { color: #6c757d; font-size: 0.9em; } .footer { text-align: center; padding: 20px; color: #6c757d; border-top: 1px solid #e9ecef; margin-top: 30px; } </style> </head> <body> <div class="container"> <div class="header"> <h1>🏥 Database Health Audit Report</h1> <p>Generated on ${new Date().toLocaleString()}</p> <p>Database Version: ${this.escapeHtml(report.databaseInfo.version.split(' ')[0])}</p> </div> <div class="content"> <div class="section"> <h2>📊 Overall Health Score</h2> <div class="score-container"> <div class="score ${this.getScoreClass(report.schemaHealth.overall)}">${report.schemaHealth.overall}/10</div> <p>${this.getScoreDescription(report.schemaHealth.overall)}</p> </div> </div> <div class="section"> <h2>🗂️ Database Overview</h2> <div class="metrics-grid"> <div class="metric-card"> <div class="metric-value">${report.databaseInfo.size}</div> <div class="metric-label">Database Size</div> </div> <div class="metric-card"> <div class="metric-value">${report.databaseInfo.tableCount}</div> <div class="metric-label">Tables</div> </div> <div class="metric-card"> <div class="metric-value">${report.databaseInfo.indexCount}</div> <div class="metric-label">Indexes</div> </div> <div class="metric-card"> <div class="metric-value">${report.databaseInfo.connectionInfo.activeConnections}/${report.databaseInfo.connectionInfo.maxConnections}</div> <div class="metric-label">Connections</div> </div> </div> </div> <div class="section"> <h2>🏗️ Schema Health Breakdown</h2> <div class="summary-stats"> <div class="stat-item"> <div class="stat-number">${report.schemaHealth.normalization}/10</div> <div class="stat-label">Normalization</div> </div> <div class="stat-item"> <div class="stat-number">${report.schemaHealth.indexEfficiency}/10</div> <div class="stat-label">Index Efficiency</div> </div> <div class="stat-item"> <div class="stat-number">${report.schemaHealth.foreignKeyIntegrity}/10</div> <div class="stat-label">FK Integrity</div> </div> <div class="stat-item"> <div class="stat-number">${report.schemaHealth.dataTypes}/10</div> <div class="stat-label">Data Types</div> </div> </div> </div> ${report.schemaHealth.issues.length > 0 ? ` <div class="section"> <h2>⚠️ Issues Found (${report.schemaHealth.issues.length})</h2> ${report.schemaHealth.issues.map(issue => ` <div class="issue ${issue.severity}"> <span class="severity-badge severity-${issue.severity}">${issue.severity}</span> <strong>${this.escapeHtml(issue.description)}</strong><br> <em>💡 ${this.escapeHtml(issue.suggestion)}</em> ${issue.sqlFix ? `<div class="sql-code">${this.escapeHtml(issue.sqlFix)}</div>` : ''} </div> `).join('')} </div> ` : ''} ${report.indexAnalysis.unusedIndexes.length > 0 ? ` <div class="section"> <h2>🗑️ Unused Indexes (${report.indexAnalysis.unusedIndexes.length})</h2> <div class="index-list"> ${report.indexAnalysis.unusedIndexes.map(index => ` <div class="index-item"> <span class="index-name">${this.escapeHtml(index.name)}</span> <span class="index-size">${index.size} on ${this.escapeHtml(index.table)}</span> </div> `).join('')} </div> </div> ` : ''} ${report.optimizationRecommendations.length > 0 ? ` <div class="section"> <h2>💡 Optimization Recommendations</h2> ${report.optimizationRecommendations.map((rec, index) => ` <div class="recommendation"> <h4>${index + 1}. ${this.escapeHtml(rec.title)} <span class="severity-badge severity-${rec.priority}">${rec.priority}</span> </h4> <p><strong>Description:</strong> ${this.escapeHtml(rec.description)}</p> <p><strong>Impact:</strong> ${this.escapeHtml(rec.estimatedImpact)}</p> <p><strong>Implementation:</strong> ${this.escapeHtml(rec.implementation)}</p> ${rec.sqlCommands && rec.sqlCommands.length > 0 ? ` <strong>SQL Commands:</strong> <div class="sql-code">${rec.sqlCommands.map(cmd => this.escapeHtml(cmd)).join('\n')}</div> ` : ''} </div> `).join('')} </div> ` : ''} <div class="section"> <h2>📋 Summary</h2> <div class="summary-stats"> <div class="stat-item"> <div class="stat-number">${report.schemaHealth.overall}/10</div> <div class="stat-label">Health Score</div> </div> <div class="stat-item"> <div class="stat-number">${report.schemaHealth.issues.length + report.performanceIssues.length + report.securityIssues.length}</div> <div class="stat-label">Issues Found</div> </div> <div class="stat-item"> <div class="stat-number">${report.optimizationRecommendations.length}</div> <div class="stat-label">Recommendations</div> </div> <div class="stat-item"> <div class="stat-number">$${(report.costAnalysis.estimatedCosts.storage + report.costAnalysis.estimatedCosts.compute + report.costAnalysis.estimatedCosts.maintenance).toFixed(0)}</div> <div class="stat-label">Monthly Cost</div> </div> </div> </div> </div> <div class="footer"> <p>Generated by SQL Optimizer v1.0 • ${new Date().toISOString()}</p> </div> </div> </body> </html>`; } escapeHtml(text) { return text .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;'); } getScoreDescription(score) { if (score >= 8) return 'Excellent - Your database is in great health!'; if (score >= 6) return 'Good - Minor optimizations recommended'; if (score >= 4) return 'Fair - Several improvements needed'; return 'Poor - Immediate attention required'; } generateCLIHeader() { return ` 🏥 ${chalk_1.default.blue.bold('DATABASE HEALTH AUDIT REPORT')} ═══════════════════════════════════════════════════════════════ `; } generateCLIDatabaseInfoSection(report) { const { databaseInfo } = report; return ` 🗂️ ${chalk_1.default.bold('DATABASE INFORMATION')} ──────────────────────────────────────────────────────────────── 📊 Database Size: ${chalk_1.default.cyan(databaseInfo.size)} 📋 Tables: ${chalk_1.default.yellow(databaseInfo.tableCount)} 📈 Indexes: ${chalk_1.default.yellow(databaseInfo.indexCount)} 🔗 Connections: ${chalk_1.default.green(databaseInfo.connectionInfo.activeConnections)}/${databaseInfo.connectionInfo.maxConnections} ⚙️ Configuration: • Shared Buffers: ${databaseInfo.settings.sharedBuffers} • Effective Cache Size: ${databaseInfo.settings.effectiveCacheSize} • Work Memory: ${databaseInfo.settings.workMem} • Maintenance Work Memory: ${databaseInfo.settings.maintenanceWorkMem} `; } generateCLISchemaHealthSection(report) { const { schemaHealth } = report; const overallColor = this.getScoreColor(schemaHealth.overall); let section = ` 🏗️ ${chalk_1.default.bold('SCHEMA HEALTH ANALYSIS')} ──────────────────────────────────────────────────────────────── 📊 Overall Health Score: ${overallColor(schemaHealth.overall + '/10')} 🔍 Component Scores: • Normalization: ${this.getScoreColor(schemaHealth.normalization)(schemaHealth.normalization + '/10')} • Index Efficiency: ${this.getScoreColor(schemaHealth.indexEfficiency)(schemaHealth.indexEfficiency + '/10')} • Foreign Key Integrity: ${this.getScoreColor(schemaHealth.foreignKeyIntegrity)(schemaHealth.foreignKeyIntegrity + '/10')} • Data Types: ${this.getScoreColor(schemaHealth.dataTypes)(schemaHealth.dataTypes + '/10')} • Naming: ${this.getScoreColor(schemaHealth.naming)(schemaHealth.naming + '/10')} `; if (schemaHealth.issues.length > 0) { section += `⚠️ ${chalk_1.default.bold('SCHEMA ISSUES FOUND:')}\n`; schemaHealth.issues.forEach(issue => { const severityColor = this.getSeverityColor(issue.severity); section += ` ${severityColor('●')} ${severityColor(issue.severity.toUpperCase())} - ${issue.description} 💡 ${issue.suggestion}`; if (issue.sqlFix) { section += `\n 🔧 ${chalk_1.default.gray(issue.sqlFix)}`; } section += '\n'; }); } return section + '\n'; } generateCLIIndexAnalysisSection(report) { const { indexAnalysis } = report; let section = ` 📈 ${chalk_1.default.bold('INDEX ANALYSIS')} ──────────────────────────────────────────────────────────────── 📊 Total Indexes: ${chalk_1.default.yellow(indexAnalysis.totalIndexes)} ⚡ Index Efficiency Score: ${this.getScoreColor(indexAnalysis.indexEfficiencyScore)(indexAnalysis.indexEfficiencyScore + '/10')} `; if (indexAnalysis.unusedIndexes.length > 0) { section += `🗑️ ${chalk_1.default.bold('UNUSED INDEXES:')}\n`; indexAnalysis.unusedIndexes.forEach(index => { section += ` • ${chalk_1.default.red(index.name)} on ${index.table} (${index.size})\n`; }); section += '\n'; } if (indexAnalysis.missingIndexes.length > 0) { section += `❌ ${chalk_1.default.bold('MISSING INDEXES:')}\n`; indexAnalysis.missingIndexes.forEach(missing => { section += ` • ${missing.table}.${missing.columns.join(', ')} - ${missing.reason}\n`; section += ` 💡 ${chalk_1.default.gray(missing.suggestedSql)}\n`; }); section += '\n'; } return section; } generateCLIPerformanceSection(report) { const { performanceIssues } = report; if (performanceIssues.length === 0) { return ` ⚡ ${chalk_1.default.bold('PERFORMANCE ANALYSIS')} ──────────────────────────────────────────────────────────────── ${chalk_1.default.green('✅ No critical performance issues detected!')} `; } let section = ` ⚡ ${chalk_1.default.bold('PERFORMANCE ANALYSIS')} ──────────────────────────────────────────────────────────────── 🚨 ${chalk_1.default.bold('PERFORMANCE ISSUES:')}\n`; performanceIssues.forEach(issue => { const severityColor = this.getSeverityColor(issue.severity); section += ` ${severityColor('●')} ${severityColor(issue.severity.toUpperCase())} - ${issue.description} 📊 Impact: ${issue.impact} 💡 Solution: ${issue.solution}`; if (issue.sqlFix) { section += `\n 🔧 ${chalk_1.default.gray(issue.sqlFix)}`; } section += '\n'; }); return section + '\n'; } generateCLISecuritySection(report) { const { securityIssues } = report; if (securityIssues.length === 0) { return ` 🔒 ${chalk_1.default.bold('SECURITY ANALYSIS')} ──────────────────────────────────────────────────────────────── ${chalk_1.default.green('✅ No critical security issues detected!')} `; } let section = ` 🔒 ${chalk_1.default.bold('SECURITY ANALYSIS')} ──────────────────────────────────────────────────────────────── 🚨 ${chalk_1.default.bold('SECURITY ISSUES:')}\n`; securityIssues.forEach(issue => { const severityColor = this.getSeverityColor(issue.severity); section += ` ${severityColor('●')} ${severityColor(issue.severity.toUpperCase())} - ${issue.description} 💡 ${issue.recommendation}\n`; }); return section + '\n'; } generateCLICostAnalysisSection(report) { const { costAnalysis } = report; return ` 💰 ${chalk_1.default.bold('COST ANALYSIS')} ──────────────────────────────────────────────────────────────── 📊 Storage Usage: • Total Size: ${chalk_1.default.cyan(costAnalysis.storageUsage.totalSize)} • Data Size: ${chalk_1.default.cyan(costAnalysis.storageUsage.dataSize)} • Index Size: ${chalk_1.default.cyan(costAnalysis.storageUsage.indexSize)} • Wasted Space: ${chalk_1.default.red(costAnalysis.storageUsage.wastedSpace)} 💵 Estimated Monthly Costs: • Storage: $${costAnalysis.estimatedCosts.storage.toFixed(2)} • Compute: $${costAnalysis.estimatedCosts.compute.toFixed(2)} • Maintenance: $${costAnalysis.estimatedCosts.maintenance.toFixed(2)} • Total: $${(costAnalysis.estimatedCosts.storage + costAnalysis.estimatedCosts.compute + costAnalysis.estimatedCosts.maintenance).toFixed(2)} `; } generateCLIRecommendationsSection(report) { const { optimizationRecommendations } = report; if (optimizationRecommendations.length === 0) { return ` 💡 ${chalk_1.default.bold('OPTIMIZATION RECOMMENDATIONS')} ──────────────────────────────────────────────────────────────── ${chalk_1.default.green('✅ No optimization recommendations at this time!')} `; } let section = ` 💡 ${chalk_1.default.bold('OPTIMIZATION RECOMMENDATIONS')} ──────────────────────────────────────────────────────────────── `; optimizationRecommendations.forEach((rec, index) => { const priorityColor = this.getPriorityColor(rec.priority); section += ` ${index + 1}. ${chalk_1.default.bold(rec.title)} [${priorityColor(rec.priority.toUpperCase())}] 📝 ${rec.description} 📊 Impact: ${rec.estimatedImpact} 🛠️ Implementation: ${rec.implementation}`; if (rec.sqlCommands && rec.sqlCommands.length > 0) { section += '\n 🔧 SQL Commands:'; rec.sqlCommands.forEach(cmd => { section += `\n ${chalk_1.default.gray(cmd)}`; }); } section += '\n'; }); return section + '\n'; } generateCLIMaintenanceSection(report) { const { maintenanceRecommendations } = report; let section = ` 🔧 ${chalk_1.default.bold('MAINTENANCE SCHEDULE')} ──────────────────────────────────────────────────────────────── `; maintenanceRecommendations.forEach(maintenance => { const importanceColor = this.getSeverityColor(maintenance.importance); section += ` ${importanceColor('●')} ${chalk_1.default.bold(maintenance.task)} [${maintenance.frequency.toUpperCase()}] 📝 ${maintenance.description}`; if (maintenance.command) { section += `\n 🔧 ${chalk_1.default.gray(maintenance.command)}`; } section += '\n'; }); return section + '\n'; } generateCLISummarySection(report) { const issueCount = report.schemaHealth.issues.length + report.performanceIssues.length + report.securityIssues.length; const recommendationCount = report.optimizationRecommendations.length; return ` 📋 ${chalk_1.default.bold('AUDIT SUMMARY')} ──────────────────────────────────────────────────────────────── 📊 Overall Health Score: ${this.getScoreColor(report.schemaHealth.overall)(report.schemaHealth.overall + '/10')} ⚠️ Issues Found: ${issueCount > 0 ? chalk_1.default.red(issueCount) : chalk_1.default.green('0')} 💡 Recommendations: ${recommendationCount > 0 ? chalk_1.default.yellow(recommendationCount) : chalk_1.default.green('0')} 💰 Estimated Monthly Cost: $${(report.costAnalysis.estimatedCosts.storage + report.costAnalysis.estimatedCosts.compute + report.costAnalysis.estimatedCosts.maintenance).toFixed(2)} ${issueCount === 0 ? chalk_1.default.green('🎉 Your database is in excellent health!') : chalk_1.default.yellow('⚡ Consider implementing the recommendations above to improve database health.')} ──────────────────────────────────────────────────────────────── Report generated by SQL Optimizer v1.0 ${new Date().toISOString()} `; } getScoreColor(score) { if (score >= 8) return chalk_1.default.green; if (score >= 6) return chalk_1.default.yellow; return chalk_1.default.red; } getScoreClass(score) { if (score >= 8) return 'excellent'; if (score >= 6) return 'good'; return 'poor'; } getSeverityColor(severity) { switch (severity) { case 'critical': return chalk_1.default.red.bold; case 'high': return chalk_1.default.red; case 'medium': return chalk_1.default.yellow; case 'low': return chalk_1.default.blue; default: return chalk_1.default.gray; } } getPriorityColor(priority) { switch (priority) { case 'critical': return chalk_1.default.red.bold; case 'high': return chalk_1.default.red; case 'medium': return chalk_1.default.yellow; case 'low': return chalk_1.default.blue; default: return chalk_1.default.gray; } } } exports.HealthReportGenerator = HealthReportGenerator; //# sourceMappingURL=health-report-generator.js.map