876 lines
35 KiB
TypeScript
876 lines
35 KiB
TypeScript
|
|
import { html } from '@design.estate/dees-element';
|
||
|
|
import type { IMonthlyUptime, IUptimeDay } 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;
|
||
|
|
}
|
||
|
|
.stats-display {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
|
||
|
|
gap: 12px;
|
||
|
|
margin-top: 12px;
|
||
|
|
}
|
||
|
|
.stat-card {
|
||
|
|
background: white;
|
||
|
|
padding: 12px;
|
||
|
|
border-radius: 4px;
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
.stat-value {
|
||
|
|
font-size: 18px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: #2196F3;
|
||
|
|
}
|
||
|
|
.stat-label {
|
||
|
|
font-size: 11px;
|
||
|
|
color: #666;
|
||
|
|
margin-top: 4px;
|
||
|
|
}
|
||
|
|
.month-nav {
|
||
|
|
display: flex;
|
||
|
|
justify-content: space-between;
|
||
|
|
align-items: center;
|
||
|
|
margin-top: 12px;
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
|
||
|
|
<div class="demo-container">
|
||
|
|
<!-- Different Month Patterns -->
|
||
|
|
<div class="demo-section">
|
||
|
|
<div class="demo-title">Different Month Patterns</div>
|
||
|
|
<dees-demowrapper
|
||
|
|
.runAfterRender=${async (wrapperElement: any) => {
|
||
|
|
const statusMonth = wrapperElement.querySelector('upl-statuspage-statusmonth') as any;
|
||
|
|
|
||
|
|
// Pattern generators
|
||
|
|
const generateMonthPattern = (monthCount: number, pattern: 'perfect' | 'problematic' | 'improving' | 'degrading' | 'seasonal'): IMonthlyUptime[] => {
|
||
|
|
const months: IMonthlyUptime[] = [];
|
||
|
|
const now = new Date();
|
||
|
|
|
||
|
|
for (let monthOffset = monthCount - 1; monthOffset >= 0; monthOffset--) {
|
||
|
|
const monthDate = new Date(now.getFullYear(), now.getMonth() - monthOffset, 1);
|
||
|
|
const year = monthDate.getFullYear();
|
||
|
|
const month = monthDate.getMonth();
|
||
|
|
const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`;
|
||
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||
|
|
|
||
|
|
const days: IUptimeDay[] = [];
|
||
|
|
let totalIncidents = 0;
|
||
|
|
let totalUptimeMinutes = 0;
|
||
|
|
|
||
|
|
for (let day = 1; day <= daysInMonth; day++) {
|
||
|
|
let uptime = 100;
|
||
|
|
let incidents = 0;
|
||
|
|
let downtime = 0;
|
||
|
|
let status: IUptimeDay['status'] = 'operational';
|
||
|
|
|
||
|
|
switch (pattern) {
|
||
|
|
case 'perfect':
|
||
|
|
// Near perfect uptime
|
||
|
|
if (Math.random() < 0.02) {
|
||
|
|
uptime = 99.9 + Math.random() * 0.099;
|
||
|
|
status = 'degraded';
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'problematic':
|
||
|
|
// Frequent issues
|
||
|
|
const problemRand = Math.random();
|
||
|
|
if (problemRand < 0.1) {
|
||
|
|
uptime = 70 + Math.random() * 20;
|
||
|
|
incidents = 2 + Math.floor(Math.random() * 3);
|
||
|
|
status = 'major_outage';
|
||
|
|
} else if (problemRand < 0.25) {
|
||
|
|
uptime = 90 + Math.random() * 8;
|
||
|
|
incidents = 1 + Math.floor(Math.random() * 2);
|
||
|
|
status = 'partial_outage';
|
||
|
|
} else if (problemRand < 0.4) {
|
||
|
|
uptime = 98 + Math.random() * 1.5;
|
||
|
|
incidents = 1;
|
||
|
|
status = 'degraded';
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'improving':
|
||
|
|
// Getting better over time
|
||
|
|
const improvementFactor = (monthCount - monthOffset) / monthCount;
|
||
|
|
const improveRand = Math.random();
|
||
|
|
if (improveRand < 0.3 * (1 - improvementFactor)) {
|
||
|
|
uptime = 85 + Math.random() * 10 + (improvementFactor * 10);
|
||
|
|
incidents = Math.max(0, 3 - Math.floor(improvementFactor * 3));
|
||
|
|
status = improvementFactor > 0.7 ? 'degraded' : 'partial_outage';
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'degrading':
|
||
|
|
// Getting worse over time
|
||
|
|
const degradationFactor = monthOffset / monthCount;
|
||
|
|
const degradeRand = Math.random();
|
||
|
|
if (degradeRand < 0.3 * (1 - degradationFactor)) {
|
||
|
|
uptime = 85 + Math.random() * 10 + (degradationFactor * 10);
|
||
|
|
incidents = Math.max(0, 3 - Math.floor(degradationFactor * 3));
|
||
|
|
status = degradationFactor > 0.7 ? 'degraded' : 'major_outage';
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
|
||
|
|
case 'seasonal':
|
||
|
|
// Worse during certain months (simulating high traffic periods)
|
||
|
|
const monthNum = month;
|
||
|
|
if (monthNum === 11 || monthNum === 0) { // December, January
|
||
|
|
if (Math.random() < 0.3) {
|
||
|
|
uptime = 92 + Math.random() * 6;
|
||
|
|
incidents = 1 + Math.floor(Math.random() * 2);
|
||
|
|
status = 'degraded';
|
||
|
|
}
|
||
|
|
} else if (monthNum === 6 || monthNum === 7) { // July, August
|
||
|
|
if (Math.random() < 0.2) {
|
||
|
|
uptime = 94 + Math.random() * 5;
|
||
|
|
incidents = 1;
|
||
|
|
status = 'degraded';
|
||
|
|
}
|
||
|
|
}
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
downtime = Math.floor((100 - uptime) * 14.4);
|
||
|
|
totalIncidents += incidents;
|
||
|
|
totalUptimeMinutes += uptime * 14.4;
|
||
|
|
|
||
|
|
days.push({
|
||
|
|
date: `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
||
|
|
uptime,
|
||
|
|
incidents,
|
||
|
|
totalDowntime: downtime,
|
||
|
|
status
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const overallUptime = totalUptimeMinutes / (daysInMonth * 1440) * 100;
|
||
|
|
|
||
|
|
months.push({
|
||
|
|
month: monthKey,
|
||
|
|
days,
|
||
|
|
overallUptime,
|
||
|
|
totalIncidents
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return months;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Initial setup
|
||
|
|
statusMonth.serviceId = 'production-api';
|
||
|
|
statusMonth.serviceName = 'Production API';
|
||
|
|
statusMonth.monthlyData = generateMonthPattern(6, 'perfect');
|
||
|
|
|
||
|
|
// Create pattern controls
|
||
|
|
const controls = document.createElement('div');
|
||
|
|
controls.className = 'demo-controls';
|
||
|
|
|
||
|
|
const patterns = [
|
||
|
|
{ key: 'perfect', label: 'Perfect Uptime' },
|
||
|
|
{ key: 'problematic', label: 'Problematic' },
|
||
|
|
{ key: 'improving', label: 'Improving Trend' },
|
||
|
|
{ key: 'degrading', label: 'Degrading Trend' },
|
||
|
|
{ key: 'seasonal', label: 'Seasonal Pattern' }
|
||
|
|
];
|
||
|
|
|
||
|
|
patterns.forEach((pattern, index) => {
|
||
|
|
const button = document.createElement('button');
|
||
|
|
button.className = 'demo-button' + (index === 0 ? ' active' : '');
|
||
|
|
button.textContent = pattern.label;
|
||
|
|
button.onclick = () => {
|
||
|
|
controls.querySelectorAll('.demo-button').forEach(btn => btn.classList.remove('active'));
|
||
|
|
button.classList.add('active');
|
||
|
|
|
||
|
|
statusMonth.loading = true;
|
||
|
|
setTimeout(() => {
|
||
|
|
statusMonth.monthlyData = generateMonthPattern(6, pattern.key as any);
|
||
|
|
statusMonth.loading = false;
|
||
|
|
updateStats();
|
||
|
|
}, 500);
|
||
|
|
};
|
||
|
|
controls.appendChild(button);
|
||
|
|
});
|
||
|
|
|
||
|
|
wrapperElement.appendChild(controls);
|
||
|
|
|
||
|
|
// Add statistics display
|
||
|
|
const statsDiv = document.createElement('div');
|
||
|
|
statsDiv.className = 'stats-display';
|
||
|
|
wrapperElement.appendChild(statsDiv);
|
||
|
|
|
||
|
|
const updateStats = () => {
|
||
|
|
const data = statusMonth.monthlyData || [];
|
||
|
|
const avgUptime = data.reduce((sum, month) => sum + month.overallUptime, 0) / data.length;
|
||
|
|
const totalIncidents = data.reduce((sum, month) => sum + month.totalIncidents, 0);
|
||
|
|
const worstMonth = data.reduce((worst, month) =>
|
||
|
|
month.overallUptime < worst.overallUptime ? month : worst, data[0]);
|
||
|
|
|
||
|
|
statsDiv.innerHTML = `
|
||
|
|
<div class="stat-card">
|
||
|
|
<div class="stat-value">${avgUptime.toFixed(3)}%</div>
|
||
|
|
<div class="stat-label">Avg Uptime</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card">
|
||
|
|
<div class="stat-value">${totalIncidents}</div>
|
||
|
|
<div class="stat-label">Total Incidents</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card">
|
||
|
|
<div class="stat-value">${data.length}</div>
|
||
|
|
<div class="stat-label">Months</div>
|
||
|
|
</div>
|
||
|
|
<div class="stat-card">
|
||
|
|
<div class="stat-value">${worstMonth ? worstMonth.overallUptime.toFixed(2) : '100'}%</div>
|
||
|
|
<div class="stat-label">Worst Month</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
};
|
||
|
|
|
||
|
|
updateStats();
|
||
|
|
|
||
|
|
// Handle day clicks
|
||
|
|
statusMonth.addEventListener('dayClick', (event: CustomEvent) => {
|
||
|
|
const { date, uptime, incidents, status, totalDowntime } = event.detail;
|
||
|
|
alert(`Day Details for ${date}:\n\nUptime: ${uptime.toFixed(3)}%\nIncidents: ${incidents}\nStatus: ${status}\nDowntime: ${totalDowntime} minutes`);
|
||
|
|
});
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<upl-statuspage-statusmonth></upl-statuspage-statusmonth>
|
||
|
|
</dees-demowrapper>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Different Time Spans -->
|
||
|
|
<div class="demo-section">
|
||
|
|
<div class="demo-title">Different Time Spans</div>
|
||
|
|
<dees-demowrapper
|
||
|
|
.runAfterRender=${async (wrapperElement: any) => {
|
||
|
|
const statusMonth = wrapperElement.querySelector('upl-statuspage-statusmonth') as any;
|
||
|
|
|
||
|
|
// Generate data for different time spans
|
||
|
|
const generateTimeSpanData = (months: number): IMonthlyUptime[] => {
|
||
|
|
const data: IMonthlyUptime[] = [];
|
||
|
|
const now = new Date();
|
||
|
|
|
||
|
|
for (let monthOffset = months - 1; monthOffset >= 0; monthOffset--) {
|
||
|
|
const monthDate = new Date(now.getFullYear(), now.getMonth() - monthOffset, 1);
|
||
|
|
const year = monthDate.getFullYear();
|
||
|
|
const month = monthDate.getMonth();
|
||
|
|
const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`;
|
||
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||
|
|
|
||
|
|
const days: IUptimeDay[] = [];
|
||
|
|
let totalIncidents = 0;
|
||
|
|
let totalUptimeMinutes = 0;
|
||
|
|
|
||
|
|
for (let day = 1; day <= daysInMonth; day++) {
|
||
|
|
// Create realistic patterns
|
||
|
|
let uptime = 99.9 + Math.random() * 0.099;
|
||
|
|
let incidents = 0;
|
||
|
|
let status: IUptimeDay['status'] = 'operational';
|
||
|
|
|
||
|
|
if (Math.random() < 0.05) {
|
||
|
|
uptime = 95 + Math.random() * 4.9;
|
||
|
|
incidents = 1;
|
||
|
|
status = 'degraded';
|
||
|
|
} else if (Math.random() < 0.01) {
|
||
|
|
uptime = 85 + Math.random() * 10;
|
||
|
|
incidents = 2;
|
||
|
|
status = 'partial_outage';
|
||
|
|
}
|
||
|
|
|
||
|
|
const downtime = Math.floor((100 - uptime) * 14.4);
|
||
|
|
totalIncidents += incidents;
|
||
|
|
totalUptimeMinutes += uptime * 14.4;
|
||
|
|
|
||
|
|
days.push({
|
||
|
|
date: `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
||
|
|
uptime,
|
||
|
|
incidents,
|
||
|
|
totalDowntime: downtime,
|
||
|
|
status
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const overallUptime = totalUptimeMinutes / (daysInMonth * 1440) * 100;
|
||
|
|
|
||
|
|
data.push({
|
||
|
|
month: monthKey,
|
||
|
|
days,
|
||
|
|
overallUptime,
|
||
|
|
totalIncidents
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
return data;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Initial setup
|
||
|
|
statusMonth.serviceId = 'multi-region-lb';
|
||
|
|
statusMonth.serviceName = 'Multi-Region Load Balancer';
|
||
|
|
statusMonth.monthlyData = generateTimeSpanData(3);
|
||
|
|
|
||
|
|
// Create time span controls
|
||
|
|
const controls = document.createElement('div');
|
||
|
|
controls.className = 'demo-controls';
|
||
|
|
|
||
|
|
const timeSpans = [
|
||
|
|
{ months: 3, label: 'Last 3 Months' },
|
||
|
|
{ months: 6, label: 'Last 6 Months' },
|
||
|
|
{ months: 12, label: 'Last 12 Months' },
|
||
|
|
{ months: 24, label: 'Last 24 Months' }
|
||
|
|
];
|
||
|
|
|
||
|
|
timeSpans.forEach((span, index) => {
|
||
|
|
const button = document.createElement('button');
|
||
|
|
button.className = 'demo-button' + (index === 0 ? ' active' : '');
|
||
|
|
button.textContent = span.label;
|
||
|
|
button.onclick = () => {
|
||
|
|
controls.querySelectorAll('.demo-button').forEach(btn => btn.classList.remove('active'));
|
||
|
|
button.classList.add('active');
|
||
|
|
|
||
|
|
statusMonth.loading = true;
|
||
|
|
setTimeout(() => {
|
||
|
|
statusMonth.monthlyData = generateTimeSpanData(span.months);
|
||
|
|
statusMonth.loading = false;
|
||
|
|
}, 500);
|
||
|
|
};
|
||
|
|
controls.appendChild(button);
|
||
|
|
});
|
||
|
|
|
||
|
|
wrapperElement.appendChild(controls);
|
||
|
|
|
||
|
|
// Add info display
|
||
|
|
const info = document.createElement('div');
|
||
|
|
info.className = 'demo-info';
|
||
|
|
info.innerHTML = 'Click on different time spans to see historical uptime data. The component automatically adjusts the display based on the number of months.';
|
||
|
|
wrapperElement.appendChild(info);
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<upl-statuspage-statusmonth></upl-statuspage-statusmonth>
|
||
|
|
</dees-demowrapper>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Current Month Real-time Updates -->
|
||
|
|
<div class="demo-section">
|
||
|
|
<div class="demo-title">Current Month with Real-time Updates</div>
|
||
|
|
<dees-demowrapper
|
||
|
|
.runAfterRender=${async (wrapperElement: any) => {
|
||
|
|
const statusMonth = wrapperElement.querySelector('upl-statuspage-statusmonth') as any;
|
||
|
|
|
||
|
|
// Generate current month data
|
||
|
|
const generateCurrentMonthData = (): IMonthlyUptime[] => {
|
||
|
|
const now = new Date();
|
||
|
|
const year = now.getFullYear();
|
||
|
|
const month = now.getMonth();
|
||
|
|
const today = now.getDate();
|
||
|
|
const monthKey = `${year}-${String(month + 1).padStart(2, '0')}`;
|
||
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||
|
|
|
||
|
|
const days: IUptimeDay[] = [];
|
||
|
|
let totalIncidents = 0;
|
||
|
|
let totalUptimeMinutes = 0;
|
||
|
|
|
||
|
|
// Generate data only up to today
|
||
|
|
for (let day = 1; day <= today; day++) {
|
||
|
|
let uptime = 99.9 + Math.random() * 0.099;
|
||
|
|
let incidents = 0;
|
||
|
|
let status: IUptimeDay['status'] = 'operational';
|
||
|
|
|
||
|
|
// Today might have ongoing issues
|
||
|
|
if (day === today) {
|
||
|
|
if (Math.random() < 0.3) {
|
||
|
|
uptime = 95 + Math.random() * 4;
|
||
|
|
incidents = 1;
|
||
|
|
status = 'degraded';
|
||
|
|
}
|
||
|
|
} else if (Math.random() < 0.05) {
|
||
|
|
uptime = 97 + Math.random() * 2.9;
|
||
|
|
incidents = 1;
|
||
|
|
status = 'degraded';
|
||
|
|
}
|
||
|
|
|
||
|
|
const downtime = Math.floor((100 - uptime) * 14.4);
|
||
|
|
totalIncidents += incidents;
|
||
|
|
totalUptimeMinutes += uptime * 14.4;
|
||
|
|
|
||
|
|
days.push({
|
||
|
|
date: `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
||
|
|
uptime,
|
||
|
|
incidents,
|
||
|
|
totalDowntime: downtime,
|
||
|
|
status
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fill remaining days with placeholder
|
||
|
|
for (let day = today + 1; day <= daysInMonth; day++) {
|
||
|
|
days.push({
|
||
|
|
date: `${year}-${String(month + 1).padStart(2, '0')}-${String(day).padStart(2, '0')}`,
|
||
|
|
uptime: 0,
|
||
|
|
incidents: 0,
|
||
|
|
totalDowntime: 0,
|
||
|
|
status: 'operational'
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
const overallUptime = today > 0 ? totalUptimeMinutes / (today * 1440) * 100 : 100;
|
||
|
|
|
||
|
|
return [{
|
||
|
|
month: monthKey,
|
||
|
|
days,
|
||
|
|
overallUptime,
|
||
|
|
totalIncidents
|
||
|
|
}];
|
||
|
|
};
|
||
|
|
|
||
|
|
// Initial setup
|
||
|
|
statusMonth.serviceId = 'realtime-monitor';
|
||
|
|
statusMonth.serviceName = 'Real-time Monitoring Service';
|
||
|
|
statusMonth.monthlyData = generateCurrentMonthData();
|
||
|
|
statusMonth.showCurrentDay = true;
|
||
|
|
|
||
|
|
// Update today's status periodically
|
||
|
|
const updateInterval = setInterval(() => {
|
||
|
|
const data = statusMonth.monthlyData;
|
||
|
|
if (data && data.length > 0) {
|
||
|
|
const currentMonth = data[0];
|
||
|
|
const today = new Date().getDate() - 1;
|
||
|
|
|
||
|
|
if (currentMonth.days[today]) {
|
||
|
|
// Simulate status changes
|
||
|
|
const rand = Math.random();
|
||
|
|
if (rand < 0.1) {
|
||
|
|
currentMonth.days[today].uptime = 95 + Math.random() * 4.9;
|
||
|
|
currentMonth.days[today].incidents = (currentMonth.days[today].incidents || 0) + 1;
|
||
|
|
currentMonth.days[today].status = 'degraded';
|
||
|
|
currentMonth.days[today].totalDowntime = Math.floor((100 - currentMonth.days[today].uptime) * 14.4);
|
||
|
|
|
||
|
|
// Recalculate overall uptime
|
||
|
|
let totalUptime = 0;
|
||
|
|
let validDays = 0;
|
||
|
|
currentMonth.days.forEach((day, index) => {
|
||
|
|
if (index <= today && day.uptime > 0) {
|
||
|
|
totalUptime += day.uptime;
|
||
|
|
validDays++;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
currentMonth.overallUptime = validDays > 0 ? totalUptime / validDays : 100;
|
||
|
|
currentMonth.totalIncidents = currentMonth.days.reduce((sum, day) => sum + (day.incidents || 0), 0);
|
||
|
|
|
||
|
|
statusMonth.requestUpdate();
|
||
|
|
logUpdate('Status degraded - Uptime: ' + currentMonth.days[today].uptime.toFixed(2) + '%');
|
||
|
|
} else if (rand < 0.05 && currentMonth.days[today].status !== 'operational') {
|
||
|
|
// Recover from issues
|
||
|
|
currentMonth.days[today].uptime = 99.9 + Math.random() * 0.099;
|
||
|
|
currentMonth.days[today].status = 'operational';
|
||
|
|
currentMonth.days[today].totalDowntime = Math.floor((100 - currentMonth.days[today].uptime) * 14.4);
|
||
|
|
|
||
|
|
statusMonth.requestUpdate();
|
||
|
|
logUpdate('Service recovered to operational status');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}, 3000);
|
||
|
|
|
||
|
|
// Create controls
|
||
|
|
const controls = document.createElement('div');
|
||
|
|
controls.className = 'demo-controls';
|
||
|
|
controls.innerHTML = '<button class="demo-button" id="simulateOutage">Simulate Outage</button>' +
|
||
|
|
'<button class="demo-button" id="simulateRecovery">Simulate Recovery</button>' +
|
||
|
|
'<button class="demo-button" id="refreshData">Refresh Data</button>';
|
||
|
|
wrapperElement.appendChild(controls);
|
||
|
|
|
||
|
|
controls.querySelector('#simulateOutage')?.addEventListener('click', () => {
|
||
|
|
const data = statusMonth.monthlyData;
|
||
|
|
if (data && data.length > 0) {
|
||
|
|
const today = new Date().getDate() - 1;
|
||
|
|
data[0].days[today].uptime = 85 + Math.random() * 10;
|
||
|
|
data[0].days[today].incidents = (data[0].days[today].incidents || 0) + 1;
|
||
|
|
data[0].days[today].status = 'major_outage';
|
||
|
|
data[0].days[today].totalDowntime = Math.floor((100 - data[0].days[today].uptime) * 14.4);
|
||
|
|
statusMonth.requestUpdate();
|
||
|
|
logUpdate('Major outage simulated');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
controls.querySelector('#simulateRecovery')?.addEventListener('click', () => {
|
||
|
|
const data = statusMonth.monthlyData;
|
||
|
|
if (data && data.length > 0) {
|
||
|
|
const today = new Date().getDate() - 1;
|
||
|
|
data[0].days[today].uptime = 99.95;
|
||
|
|
data[0].days[today].status = 'operational';
|
||
|
|
data[0].days[today].totalDowntime = Math.floor((100 - data[0].days[today].uptime) * 14.4);
|
||
|
|
statusMonth.requestUpdate();
|
||
|
|
logUpdate('Service recovered');
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
controls.querySelector('#refreshData')?.addEventListener('click', () => {
|
||
|
|
statusMonth.monthlyData = generateCurrentMonthData();
|
||
|
|
logUpdate('Data refreshed');
|
||
|
|
});
|
||
|
|
|
||
|
|
// Add update log
|
||
|
|
const logDiv = document.createElement('div');
|
||
|
|
logDiv.className = 'demo-info';
|
||
|
|
logDiv.style.maxHeight = '100px';
|
||
|
|
logDiv.style.overflowY = 'auto';
|
||
|
|
logDiv.innerHTML = '<strong>Update Log:</strong><br>';
|
||
|
|
wrapperElement.appendChild(logDiv);
|
||
|
|
|
||
|
|
const logUpdate = (message: string) => {
|
||
|
|
const time = new Date().toLocaleTimeString();
|
||
|
|
logDiv.innerHTML += '[' + time + '] ' + message + '<br>';
|
||
|
|
logDiv.scrollTop = logDiv.scrollHeight;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Cleanup
|
||
|
|
wrapperElement.addEventListener('remove', () => {
|
||
|
|
clearInterval(updateInterval);
|
||
|
|
});
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<upl-statuspage-statusmonth></upl-statuspage-statusmonth>
|
||
|
|
</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 statusMonth = wrapperElement.querySelector('upl-statuspage-statusmonth') as any;
|
||
|
|
|
||
|
|
const scenarios = {
|
||
|
|
noData: {
|
||
|
|
name: 'No Data',
|
||
|
|
data: []
|
||
|
|
},
|
||
|
|
singleMonth: {
|
||
|
|
name: 'Single Month',
|
||
|
|
data: [{
|
||
|
|
month: '2024-01',
|
||
|
|
days: Array.from({ length: 31 }, (_, i) => ({
|
||
|
|
date: `2024-01-${String(i + 1).padStart(2, '0')}`,
|
||
|
|
uptime: 99.9 + Math.random() * 0.099,
|
||
|
|
incidents: 0,
|
||
|
|
totalDowntime: 0,
|
||
|
|
status: 'operational' as const
|
||
|
|
})),
|
||
|
|
overallUptime: 99.95,
|
||
|
|
totalIncidents: 0
|
||
|
|
}]
|
||
|
|
},
|
||
|
|
allDown: {
|
||
|
|
name: 'Complete Outage Month',
|
||
|
|
data: [{
|
||
|
|
month: '2024-01',
|
||
|
|
days: Array.from({ length: 31 }, (_, i) => ({
|
||
|
|
date: `2024-01-${String(i + 1).padStart(2, '0')}`,
|
||
|
|
uptime: 0,
|
||
|
|
incidents: 5,
|
||
|
|
totalDowntime: 1440,
|
||
|
|
status: 'major_outage' as const
|
||
|
|
})),
|
||
|
|
overallUptime: 0,
|
||
|
|
totalIncidents: 155
|
||
|
|
}]
|
||
|
|
},
|
||
|
|
maintenanceMonth: {
|
||
|
|
name: 'Maintenance Heavy Month',
|
||
|
|
data: [{
|
||
|
|
month: '2024-01',
|
||
|
|
days: Array.from({ length: 31 }, (_, i) => {
|
||
|
|
// Maintenance every weekend
|
||
|
|
const dayOfWeek = new Date(2024, 0, i + 1).getDay();
|
||
|
|
if (dayOfWeek === 0 || dayOfWeek === 6) {
|
||
|
|
return {
|
||
|
|
date: `2024-01-${String(i + 1).padStart(2, '0')}`,
|
||
|
|
uptime: 95,
|
||
|
|
incidents: 0,
|
||
|
|
totalDowntime: 72,
|
||
|
|
status: 'maintenance' as const
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
date: `2024-01-${String(i + 1).padStart(2, '0')}`,
|
||
|
|
uptime: 99.95,
|
||
|
|
incidents: 0,
|
||
|
|
totalDowntime: 0.7,
|
||
|
|
status: 'operational' as const
|
||
|
|
};
|
||
|
|
}),
|
||
|
|
overallUptime: 98.2,
|
||
|
|
totalIncidents: 0
|
||
|
|
}]
|
||
|
|
},
|
||
|
|
mixedYear: {
|
||
|
|
name: 'Full Year Mixed',
|
||
|
|
data: Array.from({ length: 12 }, (_, monthIndex) => {
|
||
|
|
const year = 2023;
|
||
|
|
const month = monthIndex;
|
||
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||
|
|
|
||
|
|
// Different pattern each quarter
|
||
|
|
let monthPattern = 'operational';
|
||
|
|
if (monthIndex < 3) monthPattern = 'degraded';
|
||
|
|
else if (monthIndex < 6) monthPattern = 'improving';
|
||
|
|
else if (monthIndex < 9) monthPattern = 'stable';
|
||
|
|
else monthPattern = 'volatile';
|
||
|
|
|
||
|
|
const days = Array.from({ length: daysInMonth }, (_, dayIndex) => {
|
||
|
|
let uptime = 99.9;
|
||
|
|
let status: IUptimeDay['status'] = 'operational';
|
||
|
|
let incidents = 0;
|
||
|
|
|
||
|
|
if (monthPattern === 'degraded' && Math.random() < 0.3) {
|
||
|
|
uptime = 85 + Math.random() * 10;
|
||
|
|
status = 'degraded';
|
||
|
|
incidents = 1;
|
||
|
|
} else if (monthPattern === 'volatile' && Math.random() < 0.2) {
|
||
|
|
uptime = 90 + Math.random() * 9;
|
||
|
|
status = Math.random() < 0.5 ? 'partial_outage' : 'degraded';
|
||
|
|
incidents = Math.floor(Math.random() * 3) + 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return {
|
||
|
|
date: `${year}-${String(month + 1).padStart(2, '0')}-${String(dayIndex + 1).padStart(2, '0')}`,
|
||
|
|
uptime,
|
||
|
|
incidents,
|
||
|
|
totalDowntime: Math.floor((100 - uptime) * 14.4),
|
||
|
|
status
|
||
|
|
};
|
||
|
|
});
|
||
|
|
|
||
|
|
const totalIncidents = days.reduce((sum, day) => sum + day.incidents, 0);
|
||
|
|
const overallUptime = days.reduce((sum, day) => sum + day.uptime, 0) / days.length;
|
||
|
|
|
||
|
|
return {
|
||
|
|
month: `${year}-${String(month + 1).padStart(2, '0')}`,
|
||
|
|
days,
|
||
|
|
overallUptime,
|
||
|
|
totalIncidents
|
||
|
|
};
|
||
|
|
})
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Initial setup
|
||
|
|
let currentScenario = 'singleMonth';
|
||
|
|
statusMonth.serviceId = 'edge-case-service';
|
||
|
|
statusMonth.serviceName = 'Edge Case Service';
|
||
|
|
statusMonth.monthlyData = scenarios[currentScenario].data;
|
||
|
|
|
||
|
|
// Create scenario controls
|
||
|
|
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;
|
||
|
|
statusMonth.loading = true;
|
||
|
|
setTimeout(() => {
|
||
|
|
statusMonth.monthlyData = scenario.data;
|
||
|
|
statusMonth.loading = false;
|
||
|
|
}, 300);
|
||
|
|
};
|
||
|
|
controls.appendChild(button);
|
||
|
|
});
|
||
|
|
|
||
|
|
wrapperElement.appendChild(controls);
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<upl-statuspage-statusmonth></upl-statuspage-statusmonth>
|
||
|
|
</dees-demowrapper>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Loading and Navigation States -->
|
||
|
|
<div class="demo-section">
|
||
|
|
<div class="demo-title">Loading and Navigation Features</div>
|
||
|
|
<dees-demowrapper
|
||
|
|
.runAfterRender=${async (wrapperElement: any) => {
|
||
|
|
const statusMonth = wrapperElement.querySelector('upl-statuspage-statusmonth') as any;
|
||
|
|
|
||
|
|
// Start with loading
|
||
|
|
statusMonth.loading = true;
|
||
|
|
statusMonth.serviceId = 'navigation-demo';
|
||
|
|
statusMonth.serviceName = 'Navigation 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="toggleTooltip">Toggle Tooltip</button>';
|
||
|
|
wrapperElement.appendChild(controls);
|
||
|
|
|
||
|
|
controls.querySelector('#toggleLoading')?.addEventListener('click', () => {
|
||
|
|
statusMonth.loading = !statusMonth.loading;
|
||
|
|
});
|
||
|
|
|
||
|
|
controls.querySelector('#loadSuccess')?.addEventListener('click', () => {
|
||
|
|
statusMonth.loading = true;
|
||
|
|
setTimeout(() => {
|
||
|
|
const months = 6;
|
||
|
|
const data: IMonthlyUptime[] = [];
|
||
|
|
const now = new Date();
|
||
|
|
|
||
|
|
for (let i = months - 1; i >= 0; i--) {
|
||
|
|
const monthDate = new Date(now.getFullYear(), now.getMonth() - i, 1);
|
||
|
|
const year = monthDate.getFullYear();
|
||
|
|
const month = monthDate.getMonth();
|
||
|
|
const daysInMonth = new Date(year, month + 1, 0).getDate();
|
||
|
|
|
||
|
|
data.push({
|
||
|
|
month: `${year}-${String(month + 1).padStart(2, '0')}`,
|
||
|
|
days: Array.from({ length: daysInMonth }, (_, d) => ({
|
||
|
|
date: `${year}-${String(month + 1).padStart(2, '0')}-${String(d + 1).padStart(2, '0')}`,
|
||
|
|
uptime: 99 + Math.random(),
|
||
|
|
incidents: Math.random() < 0.05 ? 1 : 0,
|
||
|
|
totalDowntime: Math.random() < 0.05 ? Math.floor(Math.random() * 60) : 0,
|
||
|
|
status: Math.random() < 0.05 ? 'degraded' : 'operational'
|
||
|
|
})),
|
||
|
|
overallUptime: 99.5 + Math.random() * 0.4,
|
||
|
|
totalIncidents: Math.floor(Math.random() * 5)
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
statusMonth.monthlyData = data;
|
||
|
|
statusMonth.loading = false;
|
||
|
|
}, 1000);
|
||
|
|
});
|
||
|
|
|
||
|
|
controls.querySelector('#loadError')?.addEventListener('click', () => {
|
||
|
|
statusMonth.loading = true;
|
||
|
|
setTimeout(() => {
|
||
|
|
statusMonth.loading = false;
|
||
|
|
statusMonth.monthlyData = [];
|
||
|
|
statusMonth.errorMessage = 'Failed to load monthly uptime data';
|
||
|
|
}, 1500);
|
||
|
|
});
|
||
|
|
|
||
|
|
controls.querySelector('#toggleTooltip')?.addEventListener('click', () => {
|
||
|
|
statusMonth.showTooltip = !statusMonth.showTooltip;
|
||
|
|
const btn = controls.querySelector('#toggleTooltip');
|
||
|
|
if (btn) btn.textContent = 'Toggle Tooltip (' + (statusMonth.showTooltip ? 'ON' : 'OFF') + ')';
|
||
|
|
});
|
||
|
|
|
||
|
|
// Month navigation
|
||
|
|
const navDiv = document.createElement('div');
|
||
|
|
navDiv.className = 'month-nav';
|
||
|
|
navDiv.innerHTML = '<button class="demo-button" id="prevMonth">← Previous</button>' +
|
||
|
|
'<span id="currentMonth">Loading...</span>' +
|
||
|
|
'<button class="demo-button" id="nextMonth">Next →</button>';
|
||
|
|
wrapperElement.appendChild(navDiv);
|
||
|
|
|
||
|
|
let currentMonthIndex = 0;
|
||
|
|
const updateNavigation = () => {
|
||
|
|
const data = statusMonth.monthlyData || [];
|
||
|
|
if (data.length > 0 && currentMonthIndex < data.length) {
|
||
|
|
const month = data[currentMonthIndex];
|
||
|
|
const currentMonthEl = navDiv.querySelector('#currentMonth');
|
||
|
|
if (currentMonthEl) currentMonthEl.textContent = month.month;
|
||
|
|
const prevBtn = navDiv.querySelector('#prevMonth') as HTMLButtonElement;
|
||
|
|
const nextBtn = navDiv.querySelector('#nextMonth') as HTMLButtonElement;
|
||
|
|
if (prevBtn) prevBtn.disabled = currentMonthIndex === 0;
|
||
|
|
if (nextBtn) nextBtn.disabled = currentMonthIndex === data.length - 1;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
navDiv.querySelector('#prevMonth')?.addEventListener('click', () => {
|
||
|
|
if (currentMonthIndex > 0) {
|
||
|
|
currentMonthIndex--;
|
||
|
|
updateNavigation();
|
||
|
|
// Highlight the month somehow
|
||
|
|
statusMonth.highlightMonth = statusMonth.monthlyData[currentMonthIndex].month;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
navDiv.querySelector('#nextMonth')?.addEventListener('click', () => {
|
||
|
|
if (currentMonthIndex < (statusMonth.monthlyData?.length || 0) - 1) {
|
||
|
|
currentMonthIndex++;
|
||
|
|
updateNavigation();
|
||
|
|
statusMonth.highlightMonth = statusMonth.monthlyData[currentMonthIndex].month;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Initial load
|
||
|
|
setTimeout(() => {
|
||
|
|
const data = Array.from({ length: 3 }, (_, i) => ({
|
||
|
|
month: `2024-${String(i + 1).padStart(2, '0')}`,
|
||
|
|
days: Array.from({ length: 31 }, (_, d) => ({
|
||
|
|
date: `2024-${String(i + 1).padStart(2, '0')}-${String(d + 1).padStart(2, '0')}`,
|
||
|
|
uptime: 99.5 + Math.random() * 0.5,
|
||
|
|
incidents: 0,
|
||
|
|
totalDowntime: 0,
|
||
|
|
status: 'operational' as const
|
||
|
|
})),
|
||
|
|
overallUptime: 99.7 + Math.random() * 0.3,
|
||
|
|
totalIncidents: Math.floor(Math.random() * 3)
|
||
|
|
}));
|
||
|
|
|
||
|
|
statusMonth.monthlyData = data;
|
||
|
|
statusMonth.loading = false;
|
||
|
|
statusMonth.showTooltip = true;
|
||
|
|
updateNavigation();
|
||
|
|
}, 1000);
|
||
|
|
}}
|
||
|
|
>
|
||
|
|
<upl-statuspage-statusmonth></upl-statuspage-statusmonth>
|
||
|
|
</dees-demowrapper>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|