Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:45:43 +08:00
commit 77cb91c246
25 changed files with 7424 additions and 0 deletions

View File

@@ -0,0 +1,486 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Component Health Report - OpenShift {{RELEASE}}</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
line-height: 1.6;
}
.container {
max-width: 1400px;
margin: 0 auto;
background: white;
border-radius: 12px;
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
overflow: hidden;
}
.header {
background: linear-gradient(135deg, #2c3e50 0%, #34495e 100%);
color: white;
padding: 40px;
text-align: center;
}
.header h1 {
font-size: 2.5em;
margin-bottom: 10px;
font-weight: 600;
}
.header .subtitle {
font-size: 1.1em;
opacity: 0.9;
margin-top: 10px;
}
.content {
padding: 40px;
}
.section {
margin-bottom: 50px;
}
.section h2 {
color: #2c3e50;
font-size: 1.8em;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 3px solid #667eea;
}
.metrics-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.metric-card {
background: #f8f9fa;
border-radius: 8px;
padding: 25px;
border-left: 5px solid #667eea;
transition: transform 0.2s, box-shadow 0.2s;
}
.metric-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.metric-card.poor {
border-left-color: #e74c3c;
background: #fee;
}
.metric-card.warning {
border-left-color: #f39c12;
background: #fff8e1;
}
.metric-card.good {
border-left-color: #27ae60;
background: #e8f5e9;
}
.metric-label {
font-size: 0.9em;
color: #666;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.metric-value {
font-size: 2.5em;
font-weight: 700;
color: #2c3e50;
margin-bottom: 5px;
}
.metric-assessment {
font-size: 1.1em;
font-weight: 600;
margin-top: 10px;
}
.metric-details {
font-size: 0.9em;
color: #555;
margin-top: 10px;
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
background: white;
border-radius: 8px;
overflow: hidden;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
thead {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
}
th {
padding: 15px;
text-align: left;
font-weight: 600;
font-size: 0.9em;
text-transform: uppercase;
letter-spacing: 0.5px;
}
td {
padding: 12px 15px;
border-bottom: 1px solid #ecf0f1;
}
tbody tr:hover {
background: #f8f9fa;
}
tbody tr:last-child td {
border-bottom: none;
}
.grade-excellent {
color: #27ae60;
font-weight: 600;
}
.grade-good {
color: #3498db;
font-weight: 600;
}
.grade-warning {
color: #f39c12;
font-weight: 600;
}
.grade-poor {
color: #e74c3c;
font-weight: 600;
}
.alert-box {
background: #fee;
border-left: 5px solid #e74c3c;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.alert-box h3 {
color: #c0392b;
margin-bottom: 15px;
font-size: 1.3em;
}
.alert-box ul {
list-style-position: inside;
color: #555;
}
.alert-box li {
margin-bottom: 8px;
padding-left: 10px;
}
.insight-box {
background: #e8f5e9;
border-left: 5px solid #27ae60;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.insight-box h3 {
color: #27ae60;
margin-bottom: 15px;
font-size: 1.3em;
}
.insight-box ul {
list-style-position: inside;
color: #555;
}
.insight-box li {
margin-bottom: 8px;
padding-left: 10px;
}
.recommendation-box {
background: #fff8e1;
border-left: 5px solid #f39c12;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.recommendation-box h3 {
color: #e67e22;
margin-bottom: 15px;
font-size: 1.3em;
}
.recommendation-box ol {
list-style-position: inside;
color: #555;
}
.recommendation-box li {
margin-bottom: 10px;
padding-left: 10px;
}
.stats-row {
display: flex;
gap: 15px;
flex-wrap: wrap;
margin-top: 15px;
}
.stat-pill {
background: white;
padding: 8px 15px;
border-radius: 20px;
font-size: 0.9em;
border: 2px solid #ddd;
}
.stat-pill strong {
color: #2c3e50;
}
.footer {
background: #f8f9fa;
padding: 20px 40px;
text-align: center;
color: #666;
font-size: 0.9em;
border-top: 1px solid #ddd;
}
.component-name {
font-weight: 600;
color: #2c3e50;
}
.filter-controls {
margin: 20px 0;
padding: 20px;
background: #f8f9fa;
border-radius: 8px;
}
.filter-controls label {
margin-right: 15px;
font-weight: 500;
}
.filter-controls input[type="text"] {
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 1em;
width: 300px;
}
.filter-controls select {
padding: 8px 12px;
border: 2px solid #ddd;
border-radius: 4px;
font-size: 1em;
margin-left: 10px;
}
.hidden {
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Component Health Report</h1>
<div class="subtitle">OpenShift {{RELEASE}} Release</div>
<div class="subtitle">{{RELEASE_PERIOD}}</div>
</div>
<div class="content">
<!-- Overall Health Section -->
<div class="section">
<h2>Overall Health Grade</h2>
<div class="metrics-grid">
<div class="metric-card {{TRIAGE_COVERAGE_CLASS}}">
<div class="metric-label">Triage Coverage</div>
<div class="metric-value">{{TRIAGE_COVERAGE}}%</div>
<div class="metric-assessment {{TRIAGE_COVERAGE_GRADE_CLASS}}">{{TRIAGE_COVERAGE_GRADE}}</div>
<div class="metric-details">
<div>Total: {{TOTAL_REGRESSIONS}} regressions</div>
<div>Triaged: {{TRIAGED_REGRESSIONS}}</div>
<div>Untriaged: {{UNTRIAGED_REGRESSIONS}}</div>
</div>
</div>
<div class="metric-card {{TRIAGE_TIME_CLASS}}">
<div class="metric-label">Triage Timeliness</div>
<div class="metric-value">{{TRIAGE_TIME_AVG}} hrs</div>
<div class="metric-assessment {{TRIAGE_TIME_GRADE_CLASS}}">{{TRIAGE_TIME_GRADE}}</div>
<div class="metric-details">
<div>Average: {{TRIAGE_TIME_AVG_DAYS}} days</div>
<div>Maximum: {{TRIAGE_TIME_MAX}} hrs ({{TRIAGE_TIME_MAX_DAYS}} days)</div>
<div>Target: &lt;72 hours</div>
</div>
</div>
<div class="metric-card {{RESOLUTION_TIME_CLASS}}">
<div class="metric-label">Resolution Speed</div>
<div class="metric-value">{{RESOLUTION_TIME_AVG}} hrs</div>
<div class="metric-assessment {{RESOLUTION_TIME_GRADE_CLASS}}">{{RESOLUTION_TIME_GRADE}}</div>
<div class="metric-details">
<div>Average: {{RESOLUTION_TIME_AVG_DAYS}} days</div>
<div>Maximum: {{RESOLUTION_TIME_MAX}} hrs ({{RESOLUTION_TIME_MAX_DAYS}} days)</div>
<div>Target: 1-2 weeks</div>
</div>
</div>
</div>
<div class="stats-row">
<div class="stat-pill">
<strong>Open:</strong> {{OPEN_REGRESSIONS}} regressions ({{OPEN_TRIAGE_PERCENTAGE}}% triaged)
</div>
<div class="stat-pill">
<strong>Closed:</strong> {{CLOSED_REGRESSIONS}} regressions ({{CLOSED_TRIAGE_PERCENTAGE}}% triaged)
</div>
<div class="stat-pill">
<strong>Avg Age (Open):</strong> {{OPEN_AGE_AVG}} hours ({{OPEN_AGE_AVG_DAYS}} days)
</div>
</div>
</div>
<!-- Component Scorecard -->
<div class="section">
<h2>Per-Component Health Scorecard</h2>
<div class="filter-controls">
<label for="searchInput">Search Component:</label>
<input type="text" id="searchInput" placeholder="Type component name...">
<label for="gradeFilter">Filter by Grade:</label>
<select id="gradeFilter">
<option value="all">All Grades</option>
<option value="excellent">Excellent</option>
<option value="good">Good</option>
<option value="warning">Needs Improvement</option>
<option value="poor">Poor</option>
</select>
</div>
<table id="componentTable">
<thead>
<tr>
<th>Component</th>
<th>Total Regressions</th>
<th>Triage Coverage</th>
<th>Avg Triage Time</th>
<th>Avg Resolution Time</th>
<th>Open</th>
<th>Health Grade</th>
</tr>
</thead>
<tbody id="componentTableBody">
{{COMPONENT_ROWS}}
</tbody>
</table>
</div>
<!-- Critical Attention Section -->
<div class="section">
<h2>Components Needing Critical Attention</h2>
{{ATTENTION_SECTIONS}}
</div>
<!-- Key Insights -->
<div class="section">
<h2>Key Insights</h2>
<div class="insight-box">
<h3>📈 Analysis Summary</h3>
<ul>
{{INSIGHTS}}
</ul>
</div>
</div>
<!-- Recommendations -->
<div class="section">
<h2>Recommendations</h2>
<div class="recommendation-box">
<h3>🎯 Action Items</h3>
<ol>
{{RECOMMENDATIONS}}
</ol>
</div>
</div>
</div>
<div class="footer">
<strong>Data Source:</strong> Sippy Component Readiness API<br>
<strong>Date Range:</strong> {{DATE_RANGE}}<br>
<strong>Total Regressions Analyzed:</strong> {{TOTAL_REGRESSIONS}}<br>
<strong>Generated:</strong> {{GENERATED_DATE}}
</div>
</div>
<script>
// Search functionality
const searchInput = document.getElementById('searchInput');
const gradeFilter = document.getElementById('gradeFilter');
const tableBody = document.getElementById('componentTableBody');
const rows = tableBody.getElementsByTagName('tr');
function filterTable() {
const searchTerm = searchInput.value.toLowerCase();
const gradeValue = gradeFilter.value;
for (let row of rows) {
const componentName = row.querySelector('.component-name').textContent.toLowerCase();
const grade = row.getAttribute('data-grade');
const matchesSearch = componentName.includes(searchTerm);
const matchesGrade = gradeValue === 'all' || grade === gradeValue;
if (matchesSearch && matchesGrade) {
row.style.display = '';
} else {
row.style.display = 'none';
}
}
}
searchInput.addEventListener('input', filterTable);
gradeFilter.addEventListener('change', filterTable);
</script>
</body>
</html>