Files
catalog/ts_web/elements/upl-statuspage-statusdetails.demo.ts

754 lines
28 KiB
TypeScript
Raw Permalink Normal View History

2025-06-29 19:55:58 +00:00
import { html } from '@design.estate/dees-element';
import type { IStatusHistoryPoint } 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;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 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;
}
</style>
<div class="demo-container">
<!-- Time Range Demo -->
<div class="demo-section">
<div class="demo-title">Different Time Ranges</div>
<dees-demowrapper
.runAfterRender=${async (wrapperElement: any) => {
const statusDetails = wrapperElement.querySelector('upl-statuspage-statusdetails') as any;
// Generate data for different time ranges
const generateDataForRange = (hours: number, pattern: 'stable' | 'degrading' | 'improving' | 'volatile' = 'stable'): IStatusHistoryPoint[] => {
const now = Date.now();
const data: IStatusHistoryPoint[] = [];
// For proper display, we need hourly data points that align with actual hours
for (let i = hours - 1; i >= 0; i--) {
// Create timestamp at the start of each hour
const date = new Date();
date.setMinutes(0, 0, 0);
date.setHours(date.getHours() - i);
const timestamp = date.getTime();
let status: IStatusHistoryPoint['status'] = 'operational';
let responseTime = 50 + Math.random() * 50;
let errorRate = 0;
switch (pattern) {
case 'degrading':
// Getting worse over time
const degradation = (hours - i) / hours;
if (degradation > 0.7) {
status = 'major_outage';
responseTime = 800 + Math.random() * 200;
errorRate = 0.3 + Math.random() * 0.2;
} else if (degradation > 0.5) {
status = 'partial_outage';
responseTime = 500 + Math.random() * 200;
errorRate = 0.1 + Math.random() * 0.1;
} else if (degradation > 0.3) {
status = 'degraded';
responseTime = 200 + Math.random() * 100;
errorRate = 0.02 + Math.random() * 0.03;
}
break;
case 'improving':
// Getting better over time
const improvement = i / hours;
if (improvement < 0.3) {
status = 'major_outage';
responseTime = 800 + Math.random() * 200;
errorRate = 0.3 + Math.random() * 0.2;
} else if (improvement < 0.5) {
status = 'partial_outage';
responseTime = 500 + Math.random() * 200;
errorRate = 0.1 + Math.random() * 0.1;
} else if (improvement < 0.7) {
status = 'degraded';
responseTime = 200 + Math.random() * 100;
errorRate = 0.02 + Math.random() * 0.03;
}
break;
case 'volatile':
// Random ups and downs
const rand = Math.random();
if (rand < 0.05) {
status = 'major_outage';
responseTime = 800 + Math.random() * 200;
errorRate = 0.3 + Math.random() * 0.2;
} else if (rand < 0.1) {
status = 'partial_outage';
responseTime = 500 + Math.random() * 200;
errorRate = 0.1 + Math.random() * 0.1;
} else if (rand < 0.2) {
status = 'degraded';
responseTime = 200 + Math.random() * 100;
errorRate = 0.02 + Math.random() * 0.03;
} else if (rand < 0.25) {
status = 'maintenance';
responseTime = 100 + Math.random() * 50;
errorRate = 0;
}
break;
default:
// Stable with occasional hiccups
if (Math.random() < 0.02) {
status = 'degraded';
responseTime = 200 + Math.random() * 100;
errorRate = 0.01 + Math.random() * 0.02;
}
}
data.push({
timestamp,
status,
responseTime,
errorRate
});
}
return data;
};
// Initial setup
statusDetails.serviceId = 'api-gateway';
statusDetails.serviceName = 'API Gateway';
statusDetails.historyData = generateDataForRange(24);
// Create controls
const controls = document.createElement('div');
controls.className = 'demo-controls';
const timeRanges = [
{ hours: 24, label: '24 Hours' },
{ hours: 168, label: '7 Days' },
{ hours: 720, label: '30 Days' },
{ hours: 2160, label: '90 Days' }
];
timeRanges.forEach((range, index) => {
const button = document.createElement('button');
button.className = 'demo-button' + (index === 0 ? ' active' : '');
button.textContent = range.label;
button.onclick = () => {
// Update active button
controls.querySelectorAll('.demo-button').forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
// Load new data with loading state
statusDetails.loading = true;
setTimeout(() => {
statusDetails.historyData = generateDataForRange(range.hours, 'volatile');
statusDetails.loading = false;
updateStats();
}, 500);
};
controls.appendChild(button);
});
wrapperElement.appendChild(controls);
// Add statistics display
const statsDiv = document.createElement('div');
statsDiv.className = 'stats-grid';
wrapperElement.appendChild(statsDiv);
const updateStats = () => {
const data = statusDetails.historyData || [];
const operational = data.filter(d => d.status === 'operational').length;
const avgResponseTime = data.reduce((sum, d) => sum + (d.responseTime || 0), 0) / data.length;
const uptime = (operational / data.length) * 100;
const incidents = data.filter(d => d.status !== 'operational' && d.status !== 'maintenance').length;
statsDiv.innerHTML = `
<div class="stat-box">
<div class="stat-value">${uptime.toFixed(2)}%</div>
<div class="stat-label">Uptime</div>
</div>
<div class="stat-box">
<div class="stat-value">${avgResponseTime.toFixed(0)}ms</div>
<div class="stat-label">Avg Response Time</div>
</div>
<div class="stat-box">
<div class="stat-value">${incidents}</div>
<div class="stat-label">Incidents</div>
</div>
<div class="stat-box">
<div class="stat-value">${data.length}</div>
<div class="stat-label">Data Points</div>
</div>
`;
};
updateStats();
// Handle bar clicks
statusDetails.addEventListener('barClick', (event: CustomEvent) => {
const { timestamp, status, responseTime, errorRate } = event.detail;
const date = new Date(timestamp);
alert(`Details for ${date.toLocaleString()}:\n\nStatus: ${status}\nResponse Time: ${responseTime.toFixed(0)}ms\nError Rate: ${(errorRate * 100).toFixed(2)}%`);
});
}}
>
<upl-statuspage-statusdetails></upl-statuspage-statusdetails>
</dees-demowrapper>
</div>
<!-- Data Pattern Scenarios -->
<div class="demo-section">
<div class="demo-title">Different Data Patterns</div>
<dees-demowrapper
.runAfterRender=${async (wrapperElement: any) => {
const statusDetails = wrapperElement.querySelector('upl-statuspage-statusdetails') as any;
// Pattern generators
const patterns = {
stable: () => {
const data: IStatusHistoryPoint[] = [];
for (let i = 47; i >= 0; i--) {
const date = new Date();
date.setMinutes(0, 0, 0);
date.setHours(date.getHours() - i);
data.push({
timestamp: date.getTime(),
status: 'operational',
responseTime: 40 + Math.random() * 20,
errorRate: 0
});
}
return data;
},
degrading: () => {
const now = Date.now();
const data: IStatusHistoryPoint[] = [];
for (let i = 47; i >= 0; i--) {
const degradation = (47 - i) / 47;
let status: IStatusHistoryPoint['status'] = 'operational';
let responseTime = 50;
let errorRate = 0;
if (degradation > 0.8) {
status = 'major_outage';
responseTime = 800 + Math.random() * 200;
errorRate = 0.4;
} else if (degradation > 0.6) {
status = 'partial_outage';
responseTime = 500 + Math.random() * 100;
errorRate = 0.2;
} else if (degradation > 0.4) {
status = 'degraded';
responseTime = 200 + Math.random() * 100;
errorRate = 0.05;
} else {
responseTime = 50 + degradation * 100;
}
data.push({
timestamp: now - (i * 60 * 60 * 1000),
status,
responseTime,
errorRate
});
}
return data;
},
recovering: () => {
const now = Date.now();
const data: IStatusHistoryPoint[] = [];
for (let i = 47; i >= 0; i--) {
const recovery = i / 47;
let status: IStatusHistoryPoint['status'] = 'operational';
let responseTime = 50;
let errorRate = 0;
if (recovery < 0.2) {
status = 'operational';
responseTime = 50 + Math.random() * 20;
} else if (recovery < 0.4) {
status = 'degraded';
responseTime = 150 + Math.random() * 50;
errorRate = 0.02;
} else if (recovery < 0.7) {
status = 'partial_outage';
responseTime = 400 + Math.random() * 100;
errorRate = 0.15;
} else {
status = 'major_outage';
responseTime = 800 + Math.random() * 200;
errorRate = 0.35;
}
data.push({
timestamp: now - (i * 60 * 60 * 1000),
status,
responseTime,
errorRate
});
}
return data;
},
periodic: () => {
const now = Date.now();
const data: IStatusHistoryPoint[] = [];
for (let i = 47; i >= 0; i--) {
// Issues every 12 hours
const hourOfDay = i % 24;
let status: IStatusHistoryPoint['status'] = 'operational';
let responseTime = 50 + Math.random() * 30;
let errorRate = 0;
if (hourOfDay >= 9 && hourOfDay <= 11) {
// Morning peak
status = 'degraded';
responseTime = 200 + Math.random() * 100;
errorRate = 0.05;
} else if (hourOfDay >= 18 && hourOfDay <= 20) {
// Evening peak
status = 'degraded';
responseTime = 250 + Math.random() * 150;
errorRate = 0.08;
}
data.push({
timestamp: now - (i * 60 * 60 * 1000),
status,
responseTime,
errorRate
});
}
return data;
},
maintenance: () => {
const now = Date.now();
const data: IStatusHistoryPoint[] = [];
for (let i = 47; i >= 0; i--) {
let status: IStatusHistoryPoint['status'] = 'operational';
let responseTime = 50 + Math.random() * 30;
let errorRate = 0;
// Maintenance window from hour 20-24
if (i >= 20 && i <= 24) {
status = 'maintenance';
responseTime = 0;
errorRate = 0;
}
data.push({
timestamp: now - (i * 60 * 60 * 1000),
status,
responseTime,
errorRate
});
}
return data;
}
};
// Initial setup
statusDetails.serviceId = 'web-server';
statusDetails.serviceName = 'Web Server';
statusDetails.historyData = patterns.stable();
// Create controls
const controls = document.createElement('div');
controls.className = 'demo-controls';
Object.entries(patterns).forEach(([name, generator]) => {
const button = document.createElement('button');
button.className = 'demo-button' + (name === 'stable' ? ' active' : '');
button.textContent = name.charAt(0).toUpperCase() + name.slice(1);
button.onclick = () => {
controls.querySelectorAll('.demo-button').forEach(btn => btn.classList.remove('active'));
button.classList.add('active');
statusDetails.loading = true;
setTimeout(() => {
statusDetails.historyData = generator();
statusDetails.loading = false;
updateInfo(name);
}, 300);
};
controls.appendChild(button);
});
wrapperElement.appendChild(controls);
// Add info display
const info = document.createElement('div');
info.className = 'demo-info';
wrapperElement.appendChild(info);
const updateInfo = (pattern: string) => {
const descriptions = {
stable: 'Service running smoothly with consistent performance',
degrading: 'Service health deteriorating over time',
recovering: 'Service recovering from a major outage',
periodic: 'Regular performance issues during peak hours (9-11 AM and 6-8 PM)',
maintenance: 'Scheduled maintenance window (hours 20-24)'
};
info.innerHTML = `<strong>Pattern:</strong> ${descriptions[pattern as keyof typeof descriptions] || pattern}`;
};
updateInfo('stable');
}}
>
<upl-statuspage-statusdetails></upl-statuspage-statusdetails>
</dees-demowrapper>
</div>
<!-- Interactive Real-time Updates -->
<div class="demo-section">
<div class="demo-title">Real-time Updates with Manual Control</div>
<dees-demowrapper
.runAfterRender=${async (wrapperElement: any) => {
const statusDetails = wrapperElement.querySelector('upl-statuspage-statusdetails') as any;
// Initialize with recent data
const now = Date.now();
const initialData: IStatusHistoryPoint[] = [];
for (let i = 23; i >= 0; i--) {
initialData.push({
timestamp: now - (i * 60 * 60 * 1000),
status: 'operational',
responseTime: 50 + Math.random() * 30,
errorRate: 0
});
}
statusDetails.serviceId = 'real-time-api';
statusDetails.serviceName = 'Real-time API';
statusDetails.historyData = initialData;
statusDetails.timeRange = '24h';
// Create controls
const controls = document.createElement('div');
controls.className = 'demo-controls';
controls.innerHTML = `
<button class="demo-button" id="addHealthy">Add Healthy Point</button>
<button class="demo-button" id="addDegraded">Add Degraded Point</button>
<button class="demo-button" id="addOutage">Add Outage Point</button>
<button class="demo-button" id="simulateSpike">Simulate Traffic Spike</button>
<button class="demo-button" id="clearData">Clear All Data</button>
`;
wrapperElement.appendChild(controls);
const addDataPoint = (status: IStatusHistoryPoint['status'], responseTime: number, errorRate: number = 0) => {
const data = [...(statusDetails.historyData || [])];
if (data.length >= 24) {
data.shift(); // Keep only 24 points
}
data.push({
timestamp: Date.now(),
status,
responseTime,
errorRate
});
statusDetails.historyData = data;
};
controls.querySelector('#addHealthy')?.addEventListener('click', () => {
addDataPoint('operational', 50 + Math.random() * 30);
});
controls.querySelector('#addDegraded')?.addEventListener('click', () => {
addDataPoint('degraded', 200 + Math.random() * 100, 0.05);
});
controls.querySelector('#addOutage')?.addEventListener('click', () => {
addDataPoint('major_outage', 800 + Math.random() * 200, 0.5);
});
controls.querySelector('#simulateSpike')?.addEventListener('click', () => {
// Add several degraded points
for (let i = 0; i < 3; i++) {
setTimeout(() => {
addDataPoint('degraded', 300 + Math.random() * 200, 0.1 + Math.random() * 0.1);
}, i * 1000);
}
});
controls.querySelector('#clearData')?.addEventListener('click', () => {
statusDetails.historyData = [];
});
// Auto-update every 5 seconds
let autoUpdate = setInterval(() => {
const rand = Math.random();
if (rand < 0.8) {
addDataPoint('operational', 40 + Math.random() * 40);
} else if (rand < 0.95) {
addDataPoint('degraded', 150 + Math.random() * 100, 0.02);
} else {
addDataPoint('partial_outage', 400 + Math.random() * 200, 0.15);
}
}, 5000);
// Add toggle for auto-updates
const autoToggle = document.createElement('button');
autoToggle.className = 'demo-button active';
autoToggle.textContent = 'Auto-update: ON';
autoToggle.style.marginLeft = '10px';
autoToggle.onclick = () => {
if (autoUpdate) {
clearInterval(autoUpdate);
autoUpdate = null;
autoToggle.textContent = 'Auto-update: OFF';
autoToggle.classList.remove('active');
} else {
autoUpdate = setInterval(() => {
const rand = Math.random();
if (rand < 0.8) {
addDataPoint('operational', 40 + Math.random() * 40);
} else if (rand < 0.95) {
addDataPoint('degraded', 150 + Math.random() * 100, 0.02);
} else {
addDataPoint('partial_outage', 400 + Math.random() * 200, 0.15);
}
}, 5000);
autoToggle.textContent = 'Auto-update: ON';
autoToggle.classList.add('active');
}
};
controls.appendChild(autoToggle);
// Cleanup on unmount
wrapperElement.addEventListener('remove', () => {
if (autoUpdate) clearInterval(autoUpdate);
});
}}
>
<upl-statuspage-statusdetails></upl-statuspage-statusdetails>
</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 statusDetails = wrapperElement.querySelector('upl-statuspage-statusdetails') as any;
const scenarios = {
noData: {
name: 'No Data Available',
data: []
},
singlePoint: {
name: 'Single Data Point',
data: [{
timestamp: Date.now(),
status: 'operational' as const,
responseTime: 75,
errorRate: 0
}]
},
allDown: {
name: 'Complete Outage',
data: Array.from({ length: 48 }, (_, i) => ({
timestamp: Date.now() - (i * 60 * 60 * 1000),
status: 'major_outage' as const,
responseTime: 0,
errorRate: 1
}))
},
highLatency: {
name: 'High Latency Issues',
data: Array.from({ length: 48 }, (_, i) => ({
timestamp: Date.now() - (i * 60 * 60 * 1000),
status: 'operational' as const,
responseTime: 2000 + Math.random() * 1000,
errorRate: 0
}))
},
mixedStatuses: {
name: 'All Status Types',
data: Array.from({ length: 50 }, (_, i) => {
const statuses: IStatusHistoryPoint['status'][] = ['operational', 'degraded', 'partial_outage', 'major_outage', 'maintenance'];
const status = statuses[i % statuses.length];
return {
timestamp: Date.now() - (i * 60 * 60 * 1000),
status,
responseTime: status === 'operational' ? 50 : status === 'maintenance' ? 0 : 200 + Math.random() * 600,
errorRate: status === 'operational' || status === 'maintenance' ? 0 : 0.1 + Math.random() * 0.4
};
})
}
};
// Initial scenario
let currentScenario = 'noData';
statusDetails.serviceId = 'edge-case-service';
statusDetails.serviceName = 'Edge Case Service';
statusDetails.historyData = scenarios[currentScenario].data;
// Create scenario buttons
const controls = document.createElement('div');
controls.className = 'demo-controls';
Object.entries(scenarios).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;
statusDetails.loading = true;
setTimeout(() => {
statusDetails.historyData = scenario.data;
statusDetails.loading = false;
}, 300);
};
controls.appendChild(button);
});
wrapperElement.appendChild(controls);
}}
>
<upl-statuspage-statusdetails></upl-statuspage-statusdetails>
</dees-demowrapper>
</div>
<!-- Loading and Error States -->
<div class="demo-section">
<div class="demo-title">Loading and Error Handling</div>
<dees-demowrapper
.runAfterRender=${async (wrapperElement: any) => {
const statusDetails = wrapperElement.querySelector('upl-statuspage-statusdetails') as any;
// Start with loading
statusDetails.loading = true;
statusDetails.serviceId = 'loading-demo';
statusDetails.serviceName = 'Loading Demo Service';
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="loadSlowly">Load Slowly (3s)</button>
`;
wrapperElement.appendChild(controls);
controls.querySelector('#toggleLoading')?.addEventListener('click', () => {
statusDetails.loading = !statusDetails.loading;
});
controls.querySelector('#loadSuccess')?.addEventListener('click', () => {
statusDetails.loading = true;
setTimeout(() => {
const now = Date.now();
statusDetails.historyData = Array.from({ length: 24 }, (_, i) => ({
timestamp: now - (i * 60 * 60 * 1000),
status: Math.random() > 0.9 ? 'degraded' : 'operational',
responseTime: 50 + Math.random() * 50,
errorRate: 0
}));
statusDetails.loading = false;
}, 500);
});
controls.querySelector('#loadError')?.addEventListener('click', () => {
statusDetails.loading = true;
setTimeout(() => {
statusDetails.loading = false;
statusDetails.historyData = [];
statusDetails.errorMessage = 'Failed to load status data: Connection timeout';
}, 1500);
});
controls.querySelector('#loadSlowly')?.addEventListener('click', () => {
statusDetails.loading = true;
setTimeout(() => {
const now = Date.now();
statusDetails.historyData = Array.from({ length: 48 }, (_, i) => ({
timestamp: now - (i * 60 * 60 * 1000),
status: 'operational',
responseTime: 45 + Math.random() * 30,
errorRate: 0
}));
statusDetails.loading = false;
}, 3000);
});
}}
>
<upl-statuspage-statusdetails></upl-statuspage-statusdetails>
</dees-demowrapper>
</div>
</div>
`;