Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 76 additions & 16 deletions site/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -1193,12 +1193,53 @@ const render = async () => {
});

const entries = Array.from(grouped.entries());
let showAll = false;
const toggleButtons = Array.from(document.querySelectorAll('[data-toggle-timeline]'));
const ITEMS_PER_PAGE = 4;
let currentPage = 0;
let filteredEntries = entries;

const prevButtons = Array.from(document.querySelectorAll('[data-page-prev]'));
const nextButtons = Array.from(document.querySelectorAll('[data-page-next]'));
const pageLabels = Array.from(document.querySelectorAll('[data-page-label]'));
const rangeFromInputs = Array.from(document.querySelectorAll('[data-range-from]'));
const rangeToInputs = Array.from(document.querySelectorAll('[data-range-to]'));
const rangeClearButtons = Array.from(document.querySelectorAll('[data-range-clear]'));

// Build a lookup from each entry index to its raw Date
const entryDates = entries.map(([, list]) => incidentStartDate(list[0]));

// Set min/max on date inputs from incident range
const toISO = (d) => d.toISOString().slice(0, 10);
if (entryDates.length) {
const newest = entryDates[0];
const oldest = entryDates[entryDates.length - 1];
[...rangeFromInputs, ...rangeToInputs].forEach((input) => {
input.min = toISO(oldest);
input.max = toISO(newest);
});
}

const getTotalPages = () => Math.max(1, Math.ceil(filteredEntries.length / ITEMS_PER_PAGE));

const updatePaginationControls = () => {
const totalPages = getTotalPages();
prevButtons.forEach((btn) => (btn.disabled = currentPage <= 0));
nextButtons.forEach((btn) => (btn.disabled = currentPage >= totalPages - 1));
pageLabels.forEach((label) => (label.textContent = `Page ${currentPage + 1} of ${totalPages}`));
rangeClearButtons.forEach((btn) => (btn.hidden = filteredEntries === entries));
};

const renderTimeline = () => {
timeline.innerHTML = '';
const slice = showAll ? entries : entries.slice(0, 8);
const start = currentPage * ITEMS_PER_PAGE;
const slice = filteredEntries.slice(start, start + ITEMS_PER_PAGE);
if (slice.length === 0) {
const empty = document.createElement('p');
empty.className = 'muted';
empty.textContent = 'No incidents in this date range.';
empty.style.textAlign = 'center';
empty.style.padding = '24px 0';
timeline.appendChild(empty);
}
slice.forEach(([date, list]) => {
const group = document.createElement('div');
group.className = 'incident-group';
Expand All @@ -1213,24 +1254,43 @@ const render = async () => {

timeline.appendChild(group);
});
updatePaginationControls();
};

renderTimeline();
const updateToggleButtons = () => {
toggleButtons.forEach((button) => {
button.textContent = showAll ? 'Show fewer' : 'Show more';
});
const goToPage = (page) => {
const totalPages = getTotalPages();
currentPage = Math.max(0, Math.min(totalPages - 1, page));
renderTimeline();
};

updateToggleButtons();
const applyDateFilter = () => {
const fromVal = rangeFromInputs[0]?.value;
const toVal = rangeToInputs[0]?.value;
if (!fromVal && !toVal) {
filteredEntries = entries;
} else {
const from = fromVal ? new Date(fromVal + 'T00:00:00Z') : new Date(0);
const to = toVal ? new Date(toVal + 'T23:59:59Z') : new Date();
filteredEntries = entries.filter(([, list]) => {
const d = incidentStartDate(list[0]);
return d >= from && d <= to;
});
}
currentPage = 0;
renderTimeline();
};

toggleButtons.forEach((button) => {
button.addEventListener('click', () => {
showAll = !showAll;
updateToggleButtons();
renderTimeline();
});
});
prevButtons.forEach((btn) => btn.addEventListener('click', () => goToPage(currentPage - 1)));
nextButtons.forEach((btn) => btn.addEventListener('click', () => goToPage(currentPage + 1)));
rangeFromInputs.forEach((input) => input.addEventListener('change', applyDateFilter));
rangeToInputs.forEach((input) => input.addEventListener('change', applyDateFilter));
rangeClearButtons.forEach((btn) => btn.addEventListener('click', () => {
rangeFromInputs.forEach((i) => (i.value = ''));
rangeToInputs.forEach((i) => (i.value = ''));
applyDateFilter();
}));

renderTimeline();
};

const renderIncidentCard = (incident, compact = false) => {
Expand Down
16 changes: 10 additions & 6 deletions site/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -154,15 +154,19 @@ <h3>Uptime history</h3>
<div class="panel-header">
<h3>Incident timeline</h3>
<div class="panel-actions">
<button class="ghost-button" type="button" id="togglePast" data-toggle-timeline>Show more</button>
<div class="pagination" id="paginationTop">
<button class="icon-button pagination-clear" type="button" data-range-clear aria-label="Clear date filter" title="Clear filter">✕</button>
<input class="pagination-date" type="date" data-range-from aria-label="From date" title="From" />
<span class="pagination-range-sep">–</span>
<input class="pagination-date" type="date" data-range-to aria-label="To date" title="To" />
<span class="pagination-separator" aria-hidden="true"></span>
<button class="icon-button" type="button" data-page-prev aria-label="Previous page">‹</button>
<span class="pagination-label" data-page-label>Page 1 of 1</span>
<button class="icon-button" type="button" data-page-next aria-label="Next page">›</button>
</div>
</div>
</div>
<div id="incidentTimeline"></div>
<div class="timeline-footer">
<button class="ghost-button" type="button" id="togglePastBottom" data-toggle-timeline>
Show more
</button>
</div>
</section>

<section class="panel panel-raise about-panel" id="about" data-animate>
Expand Down
52 changes: 52 additions & 0 deletions site/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@
--timeline-chip-bg: #21262d;
--component-chip-bg: #11161d;
--icon-button-bg: #161b22;
--date-picker-filter: invert(0.7) brightness(1.5);
--badge-minor-ink: #e3b341;
--badge-major-ink: #ffa198;
--badge-maintenance-ink: #79c0ff;
Expand Down Expand Up @@ -1422,6 +1423,57 @@ main {
margin-top: 18px;
}

.pagination {
display: flex;
align-items: center;
gap: 8px;
}

.pagination-label {
font-weight: 600;
color: var(--muted);
min-width: 100px;
text-align: center;
user-select: none;
}

.pagination-date {
height: 32px;
border-radius: 8px;
border: 1px solid var(--border);
background: var(--icon-button-bg);
color: var(--ink);
padding: 0 10px;
font-family: var(--mono);
font-size: 0.85rem;
cursor: pointer;
}

.pagination-date::-webkit-calendar-picker-indicator {
filter: var(--date-picker-filter, none);
cursor: pointer;
}

.pagination-separator {
width: 1px;
height: 20px;
background: var(--border);
margin: 0 4px;
}

.pagination-range-sep {
color: var(--muted);
font-weight: 600;
}

.pagination-clear {
font-size: 0.75rem;
}

.pagination-clear[hidden] {
display: none;
}

.about-panel {
background: var(--about-panel-bg);
}
Expand Down