mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-19 10:46:48 +02:00
refactor status page router into uptime calculator
This commit is contained in:
parent
c02301597a
commit
6defb5ddaa
2 changed files with 92 additions and 102 deletions
|
@ -115,9 +115,25 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
|
|||
uptime = uptimeCalculator.get24Hour().uptime;
|
||||
} else {
|
||||
// For configured day ranges, use aggregated data from UptimeCalculator
|
||||
heartbeats = await getAggregatedHeartbeats(uptimeCalculator, heartbeatBarDays, maxBeats);
|
||||
// Calculate uptime for the configured range instead of just 24h
|
||||
uptime = uptimeCalculator.get24Hour().uptime; // TODO: Calculate range-specific uptime
|
||||
const buckets = uptimeCalculator.getAggregatedBuckets(heartbeatBarDays, maxBeats);
|
||||
heartbeats = buckets.map(bucket => ({
|
||||
status: bucket.down > 0 ? DOWN :
|
||||
bucket.maintenance > 0 ? MAINTENANCE :
|
||||
bucket.pending > 0 ? PENDING :
|
||||
bucket.up > 0 ? UP : null,
|
||||
time: dayjs.unix(bucket.end).toISOString(),
|
||||
msg: "",
|
||||
ping: null
|
||||
}));
|
||||
|
||||
// Calculate uptime for the configured range
|
||||
if (heartbeatBarDays <= 1) {
|
||||
uptime = uptimeCalculator.get24Hour().uptime;
|
||||
} else if (heartbeatBarDays <= 30) {
|
||||
uptime = uptimeCalculator.get30Day().uptime;
|
||||
} else {
|
||||
uptime = uptimeCalculator.get1Year().uptime;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -279,103 +295,4 @@ router.get("/api/status-page/:slug/badge", cache("5 minutes"), async (request, r
|
|||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get aggregated heartbeats for status page display
|
||||
* @param {UptimeCalculator} uptimeCalculator The uptime calculator instance
|
||||
* @param {number} days Number of days to show
|
||||
* @param {number} targetBuckets Number of buckets to aggregate into (default 100)
|
||||
* @returns {Promise<Array>} Array of aggregated heartbeat data
|
||||
*/
|
||||
async function getAggregatedHeartbeats(uptimeCalculator, days, targetBuckets = 100) {
|
||||
const now = dayjs.utc();
|
||||
const result = [];
|
||||
|
||||
// Force exact time range: exactly N days ago to exactly now
|
||||
const startTime = now.subtract(days, "day");
|
||||
const totalMinutes = days * 60 * 24;
|
||||
const bucketSizeMinutes = totalMinutes / targetBuckets;
|
||||
|
||||
// Get available data from UptimeCalculator for lookup
|
||||
const availableData = {};
|
||||
let rawDataPoints;
|
||||
|
||||
if (days <= 1) {
|
||||
const exactMinutes = Math.ceil(days * 24 * 60);
|
||||
rawDataPoints = uptimeCalculator.getDataArray(exactMinutes, "minute");
|
||||
} else if (days <= 30) {
|
||||
const exactHours = Math.ceil(days * 24);
|
||||
rawDataPoints = uptimeCalculator.getDataArray(exactHours, "hour");
|
||||
} else {
|
||||
// For > 30 days, use daily data to avoid hitting the 720-hour limit
|
||||
const requestDays = Math.min(days, 365);
|
||||
rawDataPoints = uptimeCalculator.getDataArray(requestDays, "day");
|
||||
}
|
||||
|
||||
// Create lookup map for available data
|
||||
for (const point of rawDataPoints) {
|
||||
if (point && point.timestamp) {
|
||||
availableData[point.timestamp] = point;
|
||||
}
|
||||
}
|
||||
|
||||
// Create exactly targetBuckets buckets spanning the full requested time range
|
||||
const buckets = [];
|
||||
for (let i = 0; i < targetBuckets; i++) {
|
||||
const bucketStart = startTime.add(i * bucketSizeMinutes, "minute");
|
||||
const bucketEnd = startTime.add((i + 1) * bucketSizeMinutes, "minute");
|
||||
|
||||
buckets.push({
|
||||
start: bucketStart.unix(),
|
||||
end: bucketEnd.unix(),
|
||||
up: 0,
|
||||
down: 0,
|
||||
maintenance: 0,
|
||||
pending: 0
|
||||
});
|
||||
}
|
||||
|
||||
// Aggregate available data into buckets
|
||||
let bucketIndex = 0;
|
||||
for (const [ timestamp, dataPoint ] of Object.entries(availableData)) {
|
||||
const timestampNum = parseInt(timestamp);
|
||||
|
||||
// Find the appropriate bucket for this data point (more efficient)
|
||||
while (bucketIndex < buckets.length - 1 && timestampNum >= buckets[bucketIndex].end) {
|
||||
bucketIndex++;
|
||||
}
|
||||
|
||||
const bucket = buckets[bucketIndex];
|
||||
if (bucket && timestampNum >= bucket.start && timestampNum < bucket.end && dataPoint) {
|
||||
bucket.up += dataPoint.up || 0;
|
||||
bucket.down += dataPoint.down || 0;
|
||||
bucket.maintenance += 0; // UptimeCalculator treats maintenance as up
|
||||
bucket.pending += 0; // UptimeCalculator doesn't track pending separately
|
||||
}
|
||||
}
|
||||
|
||||
// Convert buckets to heartbeat format
|
||||
for (const bucket of buckets) {
|
||||
// Determine status based on priority: DOWN > MAINTENANCE > PENDING > UP
|
||||
let status = null; // No data
|
||||
if (bucket.down > 0) {
|
||||
status = DOWN;
|
||||
} else if (bucket.maintenance > 0) {
|
||||
status = MAINTENANCE;
|
||||
} else if (bucket.pending > 0) {
|
||||
status = PENDING;
|
||||
} else if (bucket.up > 0) {
|
||||
status = UP;
|
||||
}
|
||||
|
||||
result.push({
|
||||
status: status,
|
||||
time: dayjs.unix(bucket.end).toISOString(),
|
||||
msg: "",
|
||||
ping: null
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
@ -845,6 +845,79 @@ class UptimeCalculator {
|
|||
setMigrationMode(value) {
|
||||
this.migrationMode = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get aggregated heartbeat buckets for a specific time range
|
||||
* @param {number} days Number of days to aggregate
|
||||
* @param {number} targetBuckets Number of buckets to create (default 100)
|
||||
* @returns {Array} Array of aggregated bucket data
|
||||
*/
|
||||
getAggregatedBuckets(days, targetBuckets = 100) {
|
||||
const now = dayjs.utc();
|
||||
const startTime = now.subtract(days, "day");
|
||||
const totalMinutes = days * 60 * 24;
|
||||
const bucketSizeMinutes = totalMinutes / targetBuckets;
|
||||
|
||||
// Get available data from UptimeCalculator for lookup
|
||||
const availableData = {};
|
||||
let rawDataPoints;
|
||||
|
||||
if (days <= 1) {
|
||||
const exactMinutes = Math.ceil(days * 24 * 60);
|
||||
rawDataPoints = this.getDataArray(exactMinutes, "minute");
|
||||
} else if (days <= 30) {
|
||||
const exactHours = Math.ceil(days * 24);
|
||||
rawDataPoints = this.getDataArray(exactHours, "hour");
|
||||
} else {
|
||||
// For > 30 days, use daily data to avoid hitting the 720-hour limit
|
||||
const requestDays = Math.min(days, 365);
|
||||
rawDataPoints = this.getDataArray(requestDays, "day");
|
||||
}
|
||||
|
||||
// Create lookup map for available data
|
||||
for (const point of rawDataPoints) {
|
||||
if (point && point.timestamp) {
|
||||
availableData[point.timestamp] = point;
|
||||
}
|
||||
}
|
||||
|
||||
// Create exactly targetBuckets buckets spanning the full requested time range
|
||||
const buckets = [];
|
||||
for (let i = 0; i < targetBuckets; i++) {
|
||||
const bucketStart = startTime.add(i * bucketSizeMinutes, "minute");
|
||||
const bucketEnd = startTime.add((i + 1) * bucketSizeMinutes, "minute");
|
||||
|
||||
buckets.push({
|
||||
start: bucketStart.unix(),
|
||||
end: bucketEnd.unix(),
|
||||
up: 0,
|
||||
down: 0,
|
||||
maintenance: 0,
|
||||
pending: 0
|
||||
});
|
||||
}
|
||||
|
||||
// Aggregate available data into buckets
|
||||
let bucketIndex = 0;
|
||||
for (const [ timestamp, dataPoint ] of Object.entries(availableData)) {
|
||||
const timestampNum = parseInt(timestamp);
|
||||
|
||||
// Find the appropriate bucket for this data point (more efficient)
|
||||
while (bucketIndex < buckets.length - 1 && timestampNum >= buckets[bucketIndex].end) {
|
||||
bucketIndex++;
|
||||
}
|
||||
|
||||
const bucket = buckets[bucketIndex];
|
||||
if (bucket && timestampNum >= bucket.start && timestampNum < bucket.end && dataPoint) {
|
||||
bucket.up += dataPoint.up || 0;
|
||||
bucket.down += dataPoint.down || 0;
|
||||
bucket.maintenance += 0; // UptimeCalculator treats maintenance as up
|
||||
bucket.pending += 0; // UptimeCalculator doesn't track pending separately
|
||||
}
|
||||
}
|
||||
|
||||
return buckets;
|
||||
}
|
||||
}
|
||||
|
||||
class UptimeDataResult {
|
||||
|
|
Loading…
Add table
Reference in a new issue