1216 lines
48 KiB
TypeScript
1216 lines
48 KiB
TypeScript
import { html } from '@design.estate/dees-element';
|
||
import type { IIncidentDetails, IIncidentUpdate } from '../interfaces/index.js';
|
||
|
||
export const demoFunc = () => html`
|
||
<style>
|
||
.demo-container {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20px;
|
||
}
|
||
.demo-section {
|
||
border: 1px solid #ddd;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
background: #f5f5f5;
|
||
}
|
||
.demo-title {
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
margin-bottom: 16px;
|
||
color: #333;
|
||
}
|
||
.demo-controls {
|
||
display: flex;
|
||
gap: 10px;
|
||
margin-top: 16px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.demo-button {
|
||
padding: 6px 12px;
|
||
border: 1px solid #ddd;
|
||
background: white;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
font-size: 13px;
|
||
}
|
||
.demo-button:hover {
|
||
background: #f0f0f0;
|
||
}
|
||
.demo-button.active {
|
||
background: #2196F3;
|
||
color: white;
|
||
border-color: #2196F3;
|
||
}
|
||
.demo-info {
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: white;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
line-height: 1.6;
|
||
}
|
||
.incident-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||
gap: 12px;
|
||
margin-top: 12px;
|
||
}
|
||
.stat-box {
|
||
background: white;
|
||
padding: 12px;
|
||
border-radius: 4px;
|
||
text-align: center;
|
||
}
|
||
.stat-value {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
color: #2196F3;
|
||
}
|
||
.stat-label {
|
||
font-size: 12px;
|
||
color: #666;
|
||
margin-top: 4px;
|
||
}
|
||
.incident-log {
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: #f9f9f9;
|
||
border-radius: 4px;
|
||
font-size: 12px;
|
||
max-height: 150px;
|
||
overflow-y: auto;
|
||
font-family: monospace;
|
||
}
|
||
</style>
|
||
|
||
<div class="demo-container">
|
||
<!-- Different Incident Types -->
|
||
<div class="demo-section">
|
||
<div class="demo-title">Different Incident Types and Severities</div>
|
||
<dees-demowrapper
|
||
.runAfterRender=${async (wrapperElement: any) => {
|
||
const incidents = wrapperElement.querySelector('upl-statuspage-incidents') as any;
|
||
|
||
// Generate different types of incidents
|
||
const incidentScenarios = {
|
||
critical: {
|
||
name: 'Critical Outage',
|
||
incidents: [
|
||
{
|
||
id: 'crit-001',
|
||
title: 'Complete Platform Outage',
|
||
status: 'investigating' as const,
|
||
severity: 'critical' as const,
|
||
affectedServices: ['api-gateway', 'web-server', 'database', 'cdn'],
|
||
startTime: Date.now() - 15 * 60 * 1000,
|
||
impact: 'All services are currently unavailable. We are working on immediate resolution.',
|
||
updates: [
|
||
{
|
||
id: 'upd-c1',
|
||
timestamp: Date.now() - 15 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: 'Multiple alerts triggered. All hands on deck.',
|
||
author: 'Incident Commander'
|
||
},
|
||
{
|
||
id: 'upd-c2',
|
||
timestamp: Date.now() - 10 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: 'Root cause appears to be datacenter power failure.',
|
||
author: 'Infrastructure Team'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
major: {
|
||
name: 'Major Issues',
|
||
incidents: [
|
||
{
|
||
id: 'maj-001',
|
||
title: 'Database Performance Degradation',
|
||
status: 'identified' as const,
|
||
severity: 'major' as const,
|
||
affectedServices: ['database', 'api-gateway'],
|
||
startTime: Date.now() - 45 * 60 * 1000,
|
||
impact: 'Significant slowdown in database queries affecting 60% of API requests.',
|
||
updates: [
|
||
{
|
||
id: 'upd-m1',
|
||
timestamp: Date.now() - 45 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: 'Investigating database performance issues.',
|
||
author: 'Database Team'
|
||
},
|
||
{
|
||
id: 'upd-m2',
|
||
timestamp: Date.now() - 30 * 60 * 1000,
|
||
status: 'identified' as const,
|
||
message: 'Identified runaway queries causing resource exhaustion.',
|
||
author: 'Database Team'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'maj-002',
|
||
title: 'CDN Routing Issues',
|
||
status: 'monitoring' as const,
|
||
severity: 'major' as const,
|
||
affectedServices: ['cdn'],
|
||
startTime: Date.now() - 2 * 60 * 60 * 1000,
|
||
impact: 'Assets loading slowly for users in EU region.',
|
||
updates: [
|
||
{
|
||
id: 'upd-m3',
|
||
timestamp: Date.now() - 2 * 60 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: 'Reports of slow asset loading from EU users.',
|
||
author: 'CDN Team'
|
||
},
|
||
{
|
||
id: 'upd-m4',
|
||
timestamp: Date.now() - 90 * 60 * 1000,
|
||
status: 'identified' as const,
|
||
message: 'CDN provider experiencing issues with EU PoPs.',
|
||
author: 'CDN Team'
|
||
},
|
||
{
|
||
id: 'upd-m5',
|
||
timestamp: Date.now() - 30 * 60 * 1000,
|
||
status: 'monitoring' as const,
|
||
message: 'Rerouted traffic to alternate PoPs. Monitoring performance.',
|
||
author: 'CDN Team'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
minor: {
|
||
name: 'Minor Incidents',
|
||
incidents: [
|
||
{
|
||
id: 'min-001',
|
||
title: 'Delayed Webhook Deliveries',
|
||
status: 'monitoring' as const,
|
||
severity: 'minor' as const,
|
||
affectedServices: ['webhook-service'],
|
||
startTime: Date.now() - 3 * 60 * 60 * 1000,
|
||
impact: 'Webhooks experiencing 2-5 minute delays.',
|
||
updates: [
|
||
{
|
||
id: 'upd-n1',
|
||
timestamp: Date.now() - 3 * 60 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: 'Investigating webhook delivery delays.',
|
||
author: 'API Team'
|
||
},
|
||
{
|
||
id: 'upd-n2',
|
||
timestamp: Date.now() - 2 * 60 * 60 * 1000,
|
||
status: 'monitoring' as const,
|
||
message: 'Queue processing optimized. Delays reducing.',
|
||
author: 'API Team'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'min-002',
|
||
title: 'Search Indexing Lag',
|
||
status: 'identified' as const,
|
||
severity: 'minor' as const,
|
||
affectedServices: ['search-service'],
|
||
startTime: Date.now() - 4 * 60 * 60 * 1000,
|
||
impact: 'New content taking up to 30 minutes to appear in search results.',
|
||
updates: [
|
||
{
|
||
id: 'upd-n3',
|
||
timestamp: Date.now() - 4 * 60 * 60 * 1000,
|
||
status: 'identified' as const,
|
||
message: 'Search indexing pipeline experiencing backlog.',
|
||
author: 'Search Team'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
},
|
||
maintenance: {
|
||
name: 'Maintenance',
|
||
incidents: [
|
||
{
|
||
id: 'maint-001',
|
||
title: 'Scheduled Database Maintenance',
|
||
status: 'identified' as const,
|
||
severity: 'maintenance' as const,
|
||
affectedServices: ['database'],
|
||
startTime: Date.now() + 24 * 60 * 60 * 1000,
|
||
endTime: Date.now() + 26 * 60 * 60 * 1000,
|
||
impact: 'Database will be in read-only mode during maintenance window.',
|
||
updates: [
|
||
{
|
||
id: 'upd-mt1',
|
||
timestamp: Date.now(),
|
||
status: 'identified' as const,
|
||
message: 'Scheduled maintenance for database version upgrade.',
|
||
author: 'Database Team'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'maint-002',
|
||
title: 'Network Equipment Upgrade',
|
||
status: 'identified' as const,
|
||
severity: 'maintenance' as const,
|
||
affectedServices: ['cdn', 'api-gateway'],
|
||
startTime: Date.now() + 48 * 60 * 60 * 1000,
|
||
endTime: Date.now() + 49 * 60 * 60 * 1000,
|
||
impact: 'Brief connectivity interruptions expected during upgrade window.',
|
||
updates: [
|
||
{
|
||
id: 'upd-mt2',
|
||
timestamp: Date.now() - 2 * 60 * 60 * 1000,
|
||
status: 'identified' as const,
|
||
message: 'Core network switches scheduled for firmware upgrade.',
|
||
author: 'Network Team'
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|
||
};
|
||
|
||
// Initial setup
|
||
let currentScenario = 'major';
|
||
incidents.currentIncidents = incidentScenarios[currentScenario].incidents;
|
||
incidents.pastIncidents = [];
|
||
|
||
// Create controls
|
||
const controls = document.createElement('div');
|
||
controls.className = 'demo-controls';
|
||
|
||
Object.entries(incidentScenarios).forEach(([key, scenario]) => {
|
||
const button = document.createElement('button');
|
||
button.className = 'demo-button' + (key === currentScenario ? ' active' : '');
|
||
button.textContent = scenario.name;
|
||
button.onclick = () => {
|
||
controls.querySelectorAll('.demo-button').forEach(btn => btn.classList.remove('active'));
|
||
button.classList.add('active');
|
||
|
||
currentScenario = key;
|
||
incidents.loading = true;
|
||
setTimeout(() => {
|
||
incidents.currentIncidents = scenario.incidents;
|
||
incidents.loading = false;
|
||
updateStats();
|
||
}, 500);
|
||
};
|
||
controls.appendChild(button);
|
||
});
|
||
|
||
wrapperElement.appendChild(controls);
|
||
|
||
// Add statistics
|
||
const statsDiv = document.createElement('div');
|
||
statsDiv.className = 'incident-stats';
|
||
wrapperElement.appendChild(statsDiv);
|
||
|
||
const updateStats = () => {
|
||
const current = incidents.currentIncidents || [];
|
||
const critical = current.filter(i => i.severity === 'critical').length;
|
||
const major = current.filter(i => i.severity === 'major').length;
|
||
const minor = current.filter(i => i.severity === 'minor').length;
|
||
const maintenance = current.filter(i => i.severity === 'maintenance').length;
|
||
|
||
statsDiv.innerHTML = `
|
||
<div class="stat-box">
|
||
<div class="stat-value">${critical}</div>
|
||
<div class="stat-label">Critical</div>
|
||
</div>
|
||
<div class="stat-box">
|
||
<div class="stat-value">${major}</div>
|
||
<div class="stat-label">Major</div>
|
||
</div>
|
||
<div class="stat-box">
|
||
<div class="stat-value">${minor}</div>
|
||
<div class="stat-label">Minor</div>
|
||
</div>
|
||
<div class="stat-box">
|
||
<div class="stat-value">${maintenance}</div>
|
||
<div class="stat-label">Maintenance</div>
|
||
</div>
|
||
`;
|
||
};
|
||
|
||
updateStats();
|
||
|
||
// Handle incident clicks
|
||
incidents.addEventListener('incidentClick', (event: CustomEvent) => {
|
||
alert(`Incident Details:\n\nTitle: ${event.detail.incident.title}\nSeverity: ${event.detail.incident.severity}\nStatus: ${event.detail.incident.status}\nImpact: ${event.detail.incident.impact}`);
|
||
});
|
||
}}
|
||
>
|
||
<upl-statuspage-incidents></upl-statuspage-incidents>
|
||
</dees-demowrapper>
|
||
</div>
|
||
|
||
<!-- Real-time Incident Progression -->
|
||
<div class="demo-section">
|
||
<div class="demo-title">Real-time Incident Progression</div>
|
||
<dees-demowrapper
|
||
.runAfterRender=${async (wrapperElement: any) => {
|
||
const incidents = wrapperElement.querySelector('upl-statuspage-incidents') as any;
|
||
|
||
// Create a progressing incident
|
||
const progressingIncident: IIncidentDetails = {
|
||
id: 'prog-001',
|
||
title: 'Live Incident: Payment Processing Delays',
|
||
status: 'investigating',
|
||
severity: 'major',
|
||
affectedServices: ['payment-gateway', 'api-gateway'],
|
||
startTime: Date.now(),
|
||
impact: 'Payment processing experiencing intermittent failures.',
|
||
updates: [
|
||
{
|
||
id: 'upd-p1',
|
||
timestamp: Date.now(),
|
||
status: 'investigating',
|
||
message: 'We are investigating reports of payment processing failures.',
|
||
author: 'Payment Team'
|
||
}
|
||
]
|
||
};
|
||
|
||
incidents.currentIncidents = [progressingIncident];
|
||
incidents.pastIncidents = [];
|
||
|
||
// Progression timeline
|
||
const progressionSteps = [
|
||
{
|
||
delay: 10000,
|
||
update: {
|
||
status: 'investigating' as const,
|
||
message: 'Initial investigation shows 30% of payment attempts failing.',
|
||
author: 'Payment Team'
|
||
}
|
||
},
|
||
{
|
||
delay: 20000,
|
||
update: {
|
||
status: 'identified' as const,
|
||
message: 'Root cause identified: Third-party payment processor API rate limiting.',
|
||
author: 'Engineering Team'
|
||
},
|
||
incidentUpdate: { status: 'identified' as const }
|
||
},
|
||
{
|
||
delay: 30000,
|
||
update: {
|
||
status: 'monitoring' as const,
|
||
message: 'Implemented retry logic and request throttling. Success rate improving.',
|
||
author: 'Engineering Team'
|
||
},
|
||
incidentUpdate: { status: 'monitoring' as const }
|
||
},
|
||
{
|
||
delay: 40000,
|
||
update: {
|
||
status: 'monitoring' as const,
|
||
message: 'Payment success rate now at 95%. Continuing to monitor.',
|
||
author: 'Payment Team'
|
||
}
|
||
},
|
||
{
|
||
delay: 50000,
|
||
update: {
|
||
status: 'resolved' as const,
|
||
message: 'All systems back to normal. Payment processing fully restored.',
|
||
author: 'Payment Team'
|
||
},
|
||
incidentUpdate: {
|
||
status: 'resolved' as const,
|
||
endTime: Date.now() + 50000,
|
||
resolution: 'Implemented rate limiting and retry logic to prevent future occurrences.'
|
||
}
|
||
}
|
||
];
|
||
|
||
// Progress through steps
|
||
progressionSteps.forEach((step, index) => {
|
||
setTimeout(() => {
|
||
const newUpdate: IIncidentUpdate = {
|
||
id: `upd-p${index + 2}`,
|
||
timestamp: Date.now(),
|
||
...step.update
|
||
};
|
||
|
||
progressingIncident.updates.push(newUpdate);
|
||
|
||
if (step.incidentUpdate) {
|
||
Object.assign(progressingIncident, step.incidentUpdate);
|
||
}
|
||
|
||
if (progressingIncident.status === 'resolved') {
|
||
// Move to past incidents
|
||
incidents.pastIncidents = [progressingIncident, ...incidents.pastIncidents];
|
||
incidents.currentIncidents = [];
|
||
}
|
||
|
||
incidents.requestUpdate();
|
||
logUpdate(`[${step.update.status.toUpperCase()}] ${step.update.message}`);
|
||
}, step.delay);
|
||
});
|
||
|
||
// Create controls
|
||
const controls = document.createElement('div');
|
||
controls.className = 'demo-controls';
|
||
controls.innerHTML = `
|
||
<button class="demo-button" id="addUpdate">Add Custom Update</button>
|
||
<button class="demo-button" id="escalate">Escalate to Critical</button>
|
||
<button class="demo-button" id="resolve">Resolve Immediately</button>
|
||
`;
|
||
wrapperElement.appendChild(controls);
|
||
|
||
controls.querySelector('#addUpdate')?.addEventListener('click', () => {
|
||
if (progressingIncident.status !== 'resolved') {
|
||
const message = prompt('Enter update message:');
|
||
if (message) {
|
||
progressingIncident.updates.push({
|
||
id: `upd-custom-${Date.now()}`,
|
||
timestamp: Date.now(),
|
||
status: progressingIncident.status,
|
||
message,
|
||
author: 'Manual Update'
|
||
});
|
||
incidents.requestUpdate();
|
||
logUpdate(`[MANUAL] ${message}`);
|
||
}
|
||
}
|
||
});
|
||
|
||
controls.querySelector('#escalate')?.addEventListener('click', () => {
|
||
if (progressingIncident.status !== 'resolved') {
|
||
progressingIncident.severity = 'critical';
|
||
progressingIncident.updates.push({
|
||
id: `upd-esc-${Date.now()}`,
|
||
timestamp: Date.now(),
|
||
status: progressingIncident.status,
|
||
message: 'Incident escalated to CRITICAL severity.',
|
||
author: 'Incident Commander'
|
||
});
|
||
incidents.requestUpdate();
|
||
logUpdate('[ESCALATED] Incident severity raised to CRITICAL');
|
||
}
|
||
});
|
||
|
||
controls.querySelector('#resolve')?.addEventListener('click', () => {
|
||
if (progressingIncident.status !== 'resolved') {
|
||
progressingIncident.status = 'resolved';
|
||
progressingIncident.endTime = Date.now();
|
||
progressingIncident.resolution = 'Manually resolved.';
|
||
progressingIncident.updates.push({
|
||
id: `upd-res-${Date.now()}`,
|
||
timestamp: Date.now(),
|
||
status: 'resolved',
|
||
message: 'Incident manually resolved.',
|
||
author: 'Manual Resolution'
|
||
});
|
||
incidents.pastIncidents = [progressingIncident, ...incidents.pastIncidents];
|
||
incidents.currentIncidents = [];
|
||
incidents.requestUpdate();
|
||
logUpdate('[RESOLVED] Incident manually resolved');
|
||
}
|
||
});
|
||
|
||
// Handle incident subscriptions
|
||
incidents.addEventListener('incidentSubscribe', (event: CustomEvent) => {
|
||
console.log('Subscribed to incident:', event.detail);
|
||
logUpdate(`[SUBSCRIBED] User subscribed to incident "${event.detail.incidentTitle}"`);
|
||
});
|
||
|
||
incidents.addEventListener('incidentUnsubscribe', (event: CustomEvent) => {
|
||
console.log('Unsubscribed from incident:', event.detail);
|
||
logUpdate(`[UNSUBSCRIBED] User unsubscribed from incident "${event.detail.incident.title}"`);
|
||
});
|
||
|
||
// Add update log
|
||
const logDiv = document.createElement('div');
|
||
logDiv.className = 'incident-log';
|
||
logDiv.innerHTML = '<strong>Incident Timeline:</strong><br>';
|
||
wrapperElement.appendChild(logDiv);
|
||
|
||
const logUpdate = (message: string) => {
|
||
const time = new Date().toLocaleTimeString();
|
||
logDiv.innerHTML += `[${time}] ${message}<br>`;
|
||
logDiv.scrollTop = logDiv.scrollHeight;
|
||
};
|
||
|
||
logUpdate('[CREATED] New incident: Payment Processing Delays');
|
||
}}
|
||
>
|
||
<upl-statuspage-incidents></upl-statuspage-incidents>
|
||
</dees-demowrapper>
|
||
</div>
|
||
|
||
<!-- Historical Incident Patterns -->
|
||
<div class="demo-section">
|
||
<div class="demo-title">Historical Incident Patterns</div>
|
||
<dees-demowrapper
|
||
.runAfterRender=${async (wrapperElement: any) => {
|
||
const incidents = wrapperElement.querySelector('upl-statuspage-incidents') as any;
|
||
|
||
// Generate historical patterns
|
||
const generateHistoricalIncidents = (pattern: 'frequent' | 'seasonal' | 'improving' | 'stable'): IIncidentDetails[] => {
|
||
const incidents: IIncidentDetails[] = [];
|
||
const now = Date.now();
|
||
|
||
switch (pattern) {
|
||
case 'frequent':
|
||
// Many incidents in recent history
|
||
for (let i = 0; i < 20; i++) {
|
||
const daysAgo = Math.floor(Math.random() * 30);
|
||
const duration = Math.floor(Math.random() * 4 + 1) * 60 * 60 * 1000;
|
||
const startTime = now - daysAgo * 24 * 60 * 60 * 1000;
|
||
|
||
incidents.push({
|
||
id: `freq-${i}`,
|
||
title: `Service Disruption #${20 - i}`,
|
||
status: 'resolved',
|
||
severity: Math.random() < 0.3 ? 'major' : 'minor',
|
||
affectedServices: ['api-gateway', 'database'].slice(0, Math.floor(Math.random() * 2) + 1),
|
||
startTime,
|
||
endTime: startTime + duration,
|
||
impact: 'Service degradation affecting subset of users.',
|
||
rootCause: 'Various infrastructure issues.',
|
||
resolution: 'Applied temporary fixes.',
|
||
updates: [
|
||
{
|
||
id: `upd-${i}-1`,
|
||
timestamp: startTime,
|
||
status: 'investigating',
|
||
message: 'Investigating service issues.',
|
||
author: 'Ops Team'
|
||
},
|
||
{
|
||
id: `upd-${i}-2`,
|
||
timestamp: startTime + duration,
|
||
status: 'resolved',
|
||
message: 'Issue resolved.',
|
||
author: 'Ops Team'
|
||
}
|
||
]
|
||
});
|
||
}
|
||
break;
|
||
|
||
case 'seasonal':
|
||
// Incidents clustered around specific times
|
||
const peakDays = [7, 14, 21, 28]; // Weekly pattern
|
||
peakDays.forEach((day, index) => {
|
||
for (let j = 0; j < 3; j++) {
|
||
const startTime = now - day * 24 * 60 * 60 * 1000 + (j - 1) * 60 * 60 * 1000;
|
||
incidents.push({
|
||
id: `seas-${index}-${j}`,
|
||
title: `Peak Traffic Overload`,
|
||
status: 'resolved',
|
||
severity: 'minor',
|
||
affectedServices: ['api-gateway'],
|
||
startTime,
|
||
endTime: startTime + 30 * 60 * 1000,
|
||
impact: 'Slow response times during peak hours.',
|
||
rootCause: 'Insufficient capacity for peak traffic.',
|
||
resolution: 'Auto-scaling adjusted.',
|
||
updates: [
|
||
{
|
||
id: `upd-s-${index}-${j}`,
|
||
timestamp: startTime,
|
||
status: 'resolved',
|
||
message: 'Peak traffic handled by auto-scaling.',
|
||
author: 'Auto-remediation'
|
||
}
|
||
]
|
||
});
|
||
}
|
||
});
|
||
break;
|
||
|
||
case 'improving':
|
||
// Fewer incidents over time
|
||
for (let i = 0; i < 15; i++) {
|
||
const daysAgo = 30 - i * 2;
|
||
if (Math.random() < (15 - i) / 15) {
|
||
const startTime = now - daysAgo * 24 * 60 * 60 * 1000;
|
||
incidents.push({
|
||
id: `imp-${i}`,
|
||
title: `System Issue #${i + 1}`,
|
||
status: 'resolved',
|
||
severity: i < 5 ? 'major' : 'minor',
|
||
affectedServices: ['database'],
|
||
startTime,
|
||
endTime: startTime + 2 * 60 * 60 * 1000,
|
||
impact: 'Database performance issues.',
|
||
rootCause: 'Legacy infrastructure limitations.',
|
||
resolution: 'Infrastructure improvements implemented.',
|
||
updates: [
|
||
{
|
||
id: `upd-imp-${i}`,
|
||
timestamp: startTime,
|
||
status: 'resolved',
|
||
message: 'Resolved with infrastructure improvements.',
|
||
author: 'Infrastructure Team'
|
||
}
|
||
]
|
||
});
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 'stable':
|
||
// Very few incidents, all minor
|
||
for (let i = 0; i < 3; i++) {
|
||
const daysAgo = Math.floor(Math.random() * 30);
|
||
const startTime = now - daysAgo * 24 * 60 * 60 * 1000;
|
||
incidents.push({
|
||
id: `stab-${i}`,
|
||
title: ['Routine Maintenance', 'Minor Configuration Update', 'Planned Restart'][i],
|
||
status: 'resolved',
|
||
severity: 'maintenance',
|
||
affectedServices: ['web-server'],
|
||
startTime,
|
||
endTime: startTime + 15 * 60 * 1000,
|
||
impact: 'No user impact.',
|
||
resolution: 'Completed as planned.',
|
||
updates: [
|
||
{
|
||
id: `upd-stab-${i}`,
|
||
timestamp: startTime,
|
||
status: 'resolved',
|
||
message: 'Maintenance completed successfully.',
|
||
author: 'Maintenance Team'
|
||
}
|
||
]
|
||
});
|
||
}
|
||
break;
|
||
}
|
||
|
||
return incidents.sort((a, b) => b.startTime - a.startTime);
|
||
};
|
||
|
||
// Initial setup
|
||
let currentPattern: 'frequent' | 'seasonal' | 'improving' | 'stable' = 'frequent';
|
||
incidents.currentIncidents = [];
|
||
incidents.pastIncidents = generateHistoricalIncidents(currentPattern);
|
||
|
||
// Create pattern controls
|
||
const controls = document.createElement('div');
|
||
controls.className = 'demo-controls';
|
||
|
||
const patterns = [
|
||
{ key: 'frequent', label: 'Frequent Incidents' },
|
||
{ key: 'seasonal', label: 'Seasonal Pattern' },
|
||
{ key: 'improving', label: 'Improving Trend' },
|
||
{ key: 'stable', label: 'Stable System' }
|
||
];
|
||
|
||
patterns.forEach((pattern) => {
|
||
const button = document.createElement('button');
|
||
button.className = 'demo-button' + (pattern.key === currentPattern ? ' active' : '');
|
||
button.textContent = pattern.label;
|
||
button.onclick = () => {
|
||
controls.querySelectorAll('.demo-button').forEach(btn => btn.classList.remove('active'));
|
||
button.classList.add('active');
|
||
|
||
currentPattern = pattern.key as any;
|
||
incidents.loading = true;
|
||
setTimeout(() => {
|
||
incidents.pastIncidents = generateHistoricalIncidents(currentPattern as any);
|
||
incidents.loading = false;
|
||
}, 500);
|
||
};
|
||
controls.appendChild(button);
|
||
});
|
||
|
||
wrapperElement.appendChild(controls);
|
||
|
||
// Add info about the pattern
|
||
const info = document.createElement('div');
|
||
info.className = 'demo-info';
|
||
info.innerHTML = `
|
||
<strong>Pattern Description:</strong><br>
|
||
<strong>Frequent:</strong> Many incidents indicating stability issues<br>
|
||
<strong>Seasonal:</strong> Incidents following a predictable pattern<br>
|
||
<strong>Improving:</strong> Decreasing incident frequency over time<br>
|
||
<strong>Stable:</strong> Minimal incidents, mostly maintenance
|
||
`;
|
||
wrapperElement.appendChild(info);
|
||
}}
|
||
>
|
||
<upl-statuspage-incidents></upl-statuspage-incidents>
|
||
</dees-demowrapper>
|
||
</div>
|
||
|
||
<!-- Edge Cases -->
|
||
<div class="demo-section">
|
||
<div class="demo-title">Edge Cases and Special Scenarios</div>
|
||
<dees-demowrapper
|
||
.runAfterRender=${async (wrapperElement: any) => {
|
||
const incidents = wrapperElement.querySelector('upl-statuspage-incidents') as any;
|
||
|
||
const edgeCases = {
|
||
none: {
|
||
name: 'No Incidents',
|
||
current: [],
|
||
past: []
|
||
},
|
||
manyActive: {
|
||
name: 'Many Active',
|
||
current: Array.from({ length: 8 }, (_, i) => ({
|
||
id: `many-${i}`,
|
||
title: `Concurrent Issue #${i + 1}`,
|
||
status: ['investigating', 'identified', 'monitoring'][i % 3] as any,
|
||
severity: ['critical', 'major', 'minor'][i % 3] as any,
|
||
affectedServices: [`service-${i}`],
|
||
startTime: Date.now() - (i + 1) * 30 * 60 * 1000,
|
||
impact: `Impact on service ${i + 1}`,
|
||
updates: [{
|
||
id: `upd-many-${i}`,
|
||
timestamp: Date.now() - (i + 1) * 30 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: 'Investigating issue.',
|
||
author: 'Team'
|
||
}]
|
||
})),
|
||
past: []
|
||
},
|
||
longRunning: {
|
||
name: 'Long Running',
|
||
current: [{
|
||
id: 'long-001',
|
||
title: 'Persistent Infrastructure Issue',
|
||
status: 'monitoring' as const,
|
||
severity: 'major' as const,
|
||
affectedServices: ['database', 'api-gateway'],
|
||
startTime: Date.now() - 7 * 24 * 60 * 60 * 1000, // 7 days ago
|
||
impact: 'Intermittent issues affecting 5-10% of requests.',
|
||
updates: Array.from({ length: 50 }, (_, i) => ({
|
||
id: `upd-long-${i}`,
|
||
timestamp: Date.now() - 7 * 24 * 60 * 60 * 1000 + i * 3 * 60 * 60 * 1000,
|
||
status: 'monitoring' as const,
|
||
message: `Update #${i + 1}: Continuing to monitor. Partial mitigation in place.`,
|
||
author: 'Ops Team'
|
||
}))
|
||
}],
|
||
past: []
|
||
},
|
||
complexUpdates: {
|
||
name: 'Complex Updates',
|
||
current: [{
|
||
id: 'complex-001',
|
||
title: 'Multi-phase Incident Resolution',
|
||
status: 'monitoring' as const,
|
||
severity: 'critical' as const,
|
||
affectedServices: ['api-gateway', 'database', 'cdn', 'email-service'],
|
||
startTime: Date.now() - 4 * 60 * 60 * 1000,
|
||
impact: 'Multiple services experiencing cascading failures.',
|
||
updates: [
|
||
{
|
||
id: 'upd-c1',
|
||
timestamp: Date.now() - 4 * 60 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: '🚨 CRITICAL: Multiple service alerts triggered simultaneously.',
|
||
author: 'Monitoring System'
|
||
},
|
||
{
|
||
id: 'upd-c2',
|
||
timestamp: Date.now() - 3.5 * 60 * 60 * 1000,
|
||
status: 'investigating' as const,
|
||
message: 'All hands on deck. Incident commander taking charge.\n\n- API Gateway: DOWN\n- Database: DEGRADED\n- CDN: PARTIAL OUTAGE\n- Email: DELAYED',
|
||
author: 'Incident Commander'
|
||
},
|
||
{
|
||
id: 'upd-c3',
|
||
timestamp: Date.now() - 3 * 60 * 60 * 1000,
|
||
status: 'identified' as const,
|
||
message: 'Root cause identified: Cascading failure triggered by database connection pool exhaustion.\n\nAction items:\n1. Restart connection pools\n2. Implement circuit breakers\n3. Scale up database replicas',
|
||
author: 'Engineering Lead'
|
||
},
|
||
{
|
||
id: 'upd-c4',
|
||
timestamp: Date.now() - 2 * 60 * 60 * 1000,
|
||
status: 'monitoring' as const,
|
||
message: 'Partial recovery achieved:\n✅ API Gateway: RESTORED\n⚠️ Database: RECOVERING\n✅ CDN: OPERATIONAL\n⚠️ Email: CATCHING UP\n\nContinuing remediation efforts.',
|
||
author: 'Ops Team'
|
||
}
|
||
]
|
||
}],
|
||
past: []
|
||
}
|
||
};
|
||
|
||
// Initial setup
|
||
let currentCase = 'none';
|
||
incidents.currentIncidents = edgeCases[currentCase].current;
|
||
incidents.pastIncidents = edgeCases[currentCase].past;
|
||
|
||
// Create controls
|
||
const controls = document.createElement('div');
|
||
controls.className = 'demo-controls';
|
||
|
||
Object.entries(edgeCases).forEach(([key, scenario]) => {
|
||
const button = document.createElement('button');
|
||
button.className = 'demo-button' + (key === currentCase ? ' active' : '');
|
||
button.textContent = scenario.name;
|
||
button.onclick = () => {
|
||
controls.querySelectorAll('.demo-button').forEach(btn => btn.classList.remove('active'));
|
||
button.classList.add('active');
|
||
|
||
currentCase = key;
|
||
incidents.loading = true;
|
||
setTimeout(() => {
|
||
incidents.currentIncidents = scenario.current;
|
||
incidents.pastIncidents = scenario.past;
|
||
incidents.loading = false;
|
||
}, 300);
|
||
};
|
||
controls.appendChild(button);
|
||
});
|
||
|
||
wrapperElement.appendChild(controls);
|
||
}}
|
||
>
|
||
<upl-statuspage-incidents></upl-statuspage-incidents>
|
||
</dees-demowrapper>
|
||
</div>
|
||
|
||
<!-- Loading and Filtering -->
|
||
<div class="demo-section">
|
||
<div class="demo-title">Loading States and Filtering</div>
|
||
<dees-demowrapper
|
||
.runAfterRender=${async (wrapperElement: any) => {
|
||
const incidents = wrapperElement.querySelector('upl-statuspage-incidents') as any;
|
||
|
||
// Start with loading
|
||
incidents.loading = true;
|
||
|
||
// Generate mixed incidents for filtering
|
||
const allIncidents: IIncidentDetails[] = [
|
||
{
|
||
id: 'filter-1',
|
||
title: 'Critical Database Failure',
|
||
status: 'resolved',
|
||
severity: 'critical',
|
||
affectedServices: ['database'],
|
||
startTime: Date.now() - 2 * 24 * 60 * 60 * 1000,
|
||
endTime: Date.now() - 2 * 24 * 60 * 60 * 1000 + 4 * 60 * 60 * 1000,
|
||
impact: 'Complete database unavailability.',
|
||
resolution: 'Failed over to secondary cluster.',
|
||
updates: [{
|
||
id: 'u1',
|
||
timestamp: Date.now() - 2 * 24 * 60 * 60 * 1000,
|
||
status: 'resolved',
|
||
message: 'Database restored.',
|
||
author: 'DBA Team'
|
||
}]
|
||
},
|
||
{
|
||
id: 'filter-2',
|
||
title: 'API Rate Limiting Issues',
|
||
status: 'resolved',
|
||
severity: 'major',
|
||
affectedServices: ['api-gateway'],
|
||
startTime: Date.now() - 5 * 24 * 60 * 60 * 1000,
|
||
endTime: Date.now() - 5 * 24 * 60 * 60 * 1000 + 2 * 60 * 60 * 1000,
|
||
impact: 'API requests throttled for some users.',
|
||
resolution: 'Rate limits adjusted.',
|
||
updates: [{
|
||
id: 'u2',
|
||
timestamp: Date.now() - 5 * 24 * 60 * 60 * 1000,
|
||
status: 'resolved',
|
||
message: 'Rate limiting resolved.',
|
||
author: 'API Team'
|
||
}]
|
||
},
|
||
{
|
||
id: 'filter-3',
|
||
title: 'Email Delays',
|
||
status: 'resolved',
|
||
severity: 'minor',
|
||
affectedServices: ['email-service'],
|
||
startTime: Date.now() - 10 * 24 * 60 * 60 * 1000,
|
||
endTime: Date.now() - 10 * 24 * 60 * 60 * 1000 + 30 * 60 * 1000,
|
||
impact: 'Emails delayed by 5-10 minutes.',
|
||
resolution: 'Queue cleared.',
|
||
updates: [{
|
||
id: 'u3',
|
||
timestamp: Date.now() - 10 * 24 * 60 * 60 * 1000,
|
||
status: 'resolved',
|
||
message: 'Email queue cleared.',
|
||
author: 'Email Team'
|
||
}]
|
||
},
|
||
{
|
||
id: 'filter-4',
|
||
title: 'Scheduled Maintenance',
|
||
status: 'resolved',
|
||
severity: 'maintenance',
|
||
affectedServices: ['web-server'],
|
||
startTime: Date.now() - 15 * 24 * 60 * 60 * 1000,
|
||
endTime: Date.now() - 15 * 24 * 60 * 60 * 1000 + 60 * 60 * 1000,
|
||
impact: 'Brief downtime during upgrade.',
|
||
resolution: 'Upgrade completed.',
|
||
updates: [{
|
||
id: 'u4',
|
||
timestamp: Date.now() - 15 * 24 * 60 * 60 * 1000,
|
||
status: 'resolved',
|
||
message: 'Maintenance completed.',
|
||
author: 'Ops Team'
|
||
}]
|
||
}
|
||
];
|
||
|
||
const controls = document.createElement('div');
|
||
controls.className = 'demo-controls';
|
||
controls.innerHTML = `
|
||
<button class="demo-button" id="toggleLoading">Toggle Loading</button>
|
||
<button class="demo-button" id="loadSuccess">Load Successfully</button>
|
||
<button class="demo-button" id="loadError">Simulate Error</button>
|
||
<button class="demo-button" id="filterCritical">Show Critical Only</button>
|
||
<button class="demo-button" id="showAll">Show All</button>
|
||
`;
|
||
wrapperElement.appendChild(controls);
|
||
|
||
controls.querySelector('#toggleLoading')?.addEventListener('click', () => {
|
||
incidents.loading = !incidents.loading;
|
||
});
|
||
|
||
controls.querySelector('#loadSuccess')?.addEventListener('click', () => {
|
||
incidents.loading = true;
|
||
setTimeout(() => {
|
||
incidents.currentIncidents = [
|
||
{
|
||
id: 'current-1',
|
||
title: 'Ongoing Investigation',
|
||
status: 'investigating',
|
||
severity: 'minor',
|
||
affectedServices: ['api-gateway'],
|
||
startTime: Date.now() - 30 * 60 * 1000,
|
||
impact: 'Elevated error rates on API endpoints.',
|
||
updates: [{
|
||
id: 'u-current',
|
||
timestamp: Date.now() - 30 * 60 * 1000,
|
||
status: 'investigating',
|
||
message: 'Investigating elevated error rates.',
|
||
author: 'API Team'
|
||
}]
|
||
}
|
||
];
|
||
incidents.pastIncidents = allIncidents;
|
||
incidents.loading = false;
|
||
}, 1000);
|
||
});
|
||
|
||
controls.querySelector('#loadError')?.addEventListener('click', () => {
|
||
incidents.loading = true;
|
||
setTimeout(() => {
|
||
incidents.loading = false;
|
||
incidents.currentIncidents = [];
|
||
incidents.pastIncidents = [];
|
||
incidents.errorMessage = 'Failed to load incident data';
|
||
}, 1500);
|
||
});
|
||
|
||
controls.querySelector('#filterCritical')?.addEventListener('click', () => {
|
||
incidents.pastIncidents = allIncidents.filter(i => i.severity === 'critical');
|
||
});
|
||
|
||
controls.querySelector('#showAll')?.addEventListener('click', () => {
|
||
incidents.pastIncidents = allIncidents;
|
||
});
|
||
|
||
// Initial load after delay
|
||
setTimeout(() => {
|
||
incidents.currentIncidents = [];
|
||
incidents.pastIncidents = allIncidents;
|
||
incidents.loading = false;
|
||
}, 2000);
|
||
}}
|
||
>
|
||
<upl-statuspage-incidents></upl-statuspage-incidents>
|
||
</dees-demowrapper>
|
||
</div>
|
||
|
||
<!-- Incident Subscription Demo -->
|
||
<div class="demo-section">
|
||
<div class="demo-title">Incident Subscription Management</div>
|
||
<dees-demowrapper
|
||
.runAfterRender=${async (wrapperElement: any) => {
|
||
const incidents = wrapperElement.querySelector('upl-statuspage-incidents') as any;
|
||
|
||
// Sample incidents with subscription
|
||
const currentIncidents: IIncidentDetails[] = [
|
||
{
|
||
id: 'sub-001',
|
||
title: 'Payment Gateway Intermittent Failures',
|
||
status: 'monitoring',
|
||
severity: 'major',
|
||
affectedServices: ['payment-gateway', 'checkout'],
|
||
startTime: Date.now() - 2 * 60 * 60 * 1000,
|
||
impact: 'Some customers may experience payment failures during checkout',
|
||
updates: [
|
||
{
|
||
id: 'sub-u1',
|
||
timestamp: Date.now() - 2 * 60 * 60 * 1000,
|
||
status: 'investigating',
|
||
message: 'We are investigating reports of payment failures',
|
||
author: 'Payment Team'
|
||
},
|
||
{
|
||
id: 'sub-u2',
|
||
timestamp: Date.now() - 1 * 60 * 60 * 1000,
|
||
status: 'identified',
|
||
message: 'Issue identified with payment processor API rate limits',
|
||
author: 'Payment Team'
|
||
},
|
||
{
|
||
id: 'sub-u3',
|
||
timestamp: Date.now() - 30 * 60 * 1000,
|
||
status: 'monitoring',
|
||
message: 'Temporary fix applied, monitoring for stability',
|
||
author: 'Payment Team'
|
||
}
|
||
]
|
||
},
|
||
{
|
||
id: 'sub-002',
|
||
title: 'Email Delivery Delays',
|
||
status: 'identified',
|
||
severity: 'minor',
|
||
affectedServices: ['email-service'],
|
||
startTime: Date.now() - 45 * 60 * 1000,
|
||
impact: 'Transactional emails may be delayed by 5-10 minutes',
|
||
updates: [
|
||
{
|
||
id: 'sub-u4',
|
||
timestamp: Date.now() - 45 * 60 * 1000,
|
||
status: 'investigating',
|
||
message: 'Investigating email queue backlog',
|
||
author: 'Infrastructure Team'
|
||
},
|
||
{
|
||
id: 'sub-u5',
|
||
timestamp: Date.now() - 20 * 60 * 1000,
|
||
status: 'identified',
|
||
message: 'High volume causing queue delays, scaling up workers',
|
||
author: 'Infrastructure Team'
|
||
}
|
||
]
|
||
}
|
||
];
|
||
|
||
incidents.currentIncidents = currentIncidents;
|
||
incidents.pastIncidents = [];
|
||
|
||
// Pre-subscribe to first incident
|
||
incidents.subscribedIncidentIds = ['sub-001'];
|
||
|
||
// Create subscription status display
|
||
const statusDiv = document.createElement('div');
|
||
statusDiv.className = 'subscription-status';
|
||
statusDiv.style.cssText = `
|
||
margin-top: 16px;
|
||
padding: 16px;
|
||
background: #f0f9ff;
|
||
border: 1px solid #bae6fd;
|
||
border-radius: 6px;
|
||
font-size: 14px;
|
||
`;
|
||
|
||
const updateStatus = () => {
|
||
const subscribed = Array.from(incidents.subscribedIncidents || []);
|
||
statusDiv.innerHTML = `
|
||
<strong>Subscription Status:</strong><br>
|
||
${subscribed.length === 0 ?
|
||
'Not subscribed to any incidents' :
|
||
`Subscribed to ${subscribed.length} incident(s):<br>` +
|
||
subscribed.map(id => {
|
||
const incident = currentIncidents.find(i => i.id === id);
|
||
return incident ? `• ${incident.title}` : '';
|
||
}).filter(Boolean).join('<br>')}
|
||
`;
|
||
};
|
||
|
||
updateStatus();
|
||
wrapperElement.appendChild(statusDiv);
|
||
|
||
// Handle subscription events
|
||
incidents.addEventListener('incidentSubscribe', (event: CustomEvent) => {
|
||
console.log('Subscribed:', event.detail);
|
||
updateStatus();
|
||
|
||
// Show notification
|
||
const notification = document.createElement('div');
|
||
notification.style.cssText = `
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 16px;
|
||
background: #10b981;
|
||
color: white;
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||
z-index: 1000;
|
||
animation: slideIn 0.3s ease;
|
||
`;
|
||
notification.textContent = `✓ Subscribed to: ${event.detail.incidentTitle}`;
|
||
document.body.appendChild(notification);
|
||
setTimeout(() => notification.remove(), 3000);
|
||
});
|
||
|
||
incidents.addEventListener('incidentUnsubscribe', (event: CustomEvent) => {
|
||
console.log('Unsubscribed:', event.detail);
|
||
updateStatus();
|
||
|
||
// Show notification
|
||
const notification = document.createElement('div');
|
||
notification.style.cssText = `
|
||
position: fixed;
|
||
top: 20px;
|
||
right: 20px;
|
||
padding: 16px;
|
||
background: #6b7280;
|
||
color: white;
|
||
border-radius: 6px;
|
||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||
z-index: 1000;
|
||
animation: slideIn 0.3s ease;
|
||
`;
|
||
notification.textContent = `Unsubscribed from: ${event.detail.incident.title}`;
|
||
document.body.appendChild(notification);
|
||
setTimeout(() => notification.remove(), 3000);
|
||
});
|
||
|
||
// Add info text
|
||
const infoDiv = document.createElement('div');
|
||
infoDiv.style.cssText = `
|
||
margin-top: 12px;
|
||
padding: 12px;
|
||
background: #f3f4f6;
|
||
border-radius: 4px;
|
||
font-size: 13px;
|
||
color: #6b7280;
|
||
`;
|
||
infoDiv.innerHTML = `
|
||
<strong>How it works:</strong><br>
|
||
• Click on an incident to expand it<br>
|
||
• Click "Subscribe to updates" to get notifications<br>
|
||
• Subscribed incidents show a checkmark<br>
|
||
• Click again to unsubscribe
|
||
`;
|
||
wrapperElement.appendChild(infoDiv);
|
||
}}
|
||
>
|
||
<upl-statuspage-incidents></upl-statuspage-incidents>
|
||
</dees-demowrapper>
|
||
</div>
|
||
</div>
|
||
`; |