feat: Add configurable heartbeat bar range for status pages

Add a configurable range option (7/30/60/90/180/365 days) for heartbeat bars on status pages.
This allows status page owners to customize how much historical data is shown to visitors.

Changes:
- Add database migration for heartbeat_bar_range_days column
- Update StatusPage model to include heartbeat bar range setting
- Modify heartbeat API endpoint to respect configured range
- Add UI controls in status page editor for range selection
- Update English translations for new settings
- Default to 90 days for backward compatibility

Resolves #1888
This commit is contained in:
Doruk 2025-06-14 01:06:53 +02:00
parent 55817061c0
commit ad713eda4b
7 changed files with 54 additions and 4 deletions

View file

@ -0,0 +1,12 @@
exports.up = function (knex) {
return knex.schema
.alterTable("status_page", function (table) {
table.integer("heartbeat_bar_range_days").defaultTo(90).unsigned();
});
};
exports.down = function (knex) {
return knex.schema.alterTable("status_page", function (table) {
table.dropColumn("heartbeat_bar_range_days");
});
};

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "uptime-kuma",
"version": "2.0.0-beta.2",
"version": "2.0.0-beta.3",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "uptime-kuma",
"version": "2.0.0-beta.2",
"version": "2.0.0-beta.3",
"license": "MIT",
"dependencies": {
"@grpc/grpc-js": "~1.8.22",

View file

@ -409,6 +409,7 @@ class StatusPage extends BeanModel {
showPoweredBy: !!this.show_powered_by,
googleAnalyticsId: this.google_analytics_tag_id,
showCertificateExpiry: !!this.show_certificate_expiry,
heartbeatBarRangeDays: this.heartbeat_bar_range_days || 90,
};
}
@ -432,6 +433,7 @@ class StatusPage extends BeanModel {
showPoweredBy: !!this.show_powered_by,
googleAnalyticsId: this.google_analytics_tag_id,
showCertificateExpiry: !!this.show_certificate_expiry,
heartbeatBarRangeDays: this.heartbeat_bar_range_days || 90,
};
}

View file

@ -84,14 +84,22 @@ router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (reques
statusPageID
]);
// Get the status page to determine the heartbeat range
let statusPage = await R.findOne("status_page", " id = ? ", [ statusPageID ]);
let heartbeatRangeDays = (statusPage && statusPage.heartbeat_bar_range_days) ? statusPage.heartbeat_bar_range_days : 90;
// Calculate the date range for heartbeats
let dateFrom = new Date();
dateFrom.setDate(dateFrom.getDate() - heartbeatRangeDays);
for (let monitorID of monitorIDList) {
let list = await R.getAll(`
SELECT * FROM heartbeat
WHERE monitor_id = ?
WHERE monitor_id = ? AND time >= ?
ORDER BY time DESC
LIMIT 100
`, [
monitorID,
dateFrom.toISOString(),
]);
list = R.convertToBeans("heartbeat", list);

View file

@ -165,6 +165,7 @@ module.exports.statusPageSocketHandler = (socket) => {
statusPage.custom_css = config.customCSS;
statusPage.show_powered_by = config.showPoweredBy;
statusPage.show_certificate_expiry = config.showCertificateExpiry;
statusPage.heartbeat_bar_range_days = config.heartbeatBarRangeDays || 90;
statusPage.modified_date = R.isoDateTime();
statusPage.google_analytics_tag_id = config.googleAnalyticsId;

View file

@ -375,6 +375,14 @@
"Footer Text": "Footer Text",
"Refresh Interval": "Refresh Interval",
"Refresh Interval Description": "The status page will do a full site refresh every {0} seconds",
"Heartbeat Bar Range": "Heartbeat Bar Range",
"7 days": "7 days",
"30 days": "30 days",
"60 days": "60 days",
"90 days": "90 days",
"180 days": "180 days",
"365 days": "365 days",
"How many days of heartbeat history to show in the status page": "How many days of heartbeat history to show in the status page",
"Show Powered By": "Show Powered By",
"Domain Names": "Domain Names",
"signedInDisp": "Signed in as {0}",

View file

@ -42,6 +42,21 @@
</div>
</div>
<div class="my-3">
<label for="heartbeat-bar-range" class="form-label">{{ $t("Heartbeat Bar Range") }}</label>
<select id="heartbeat-bar-range" v-model="config.heartbeatBarRangeDays" class="form-select" data-testid="heartbeat-bar-range-select">
<option value="7">{{ $t("7 days") }}</option>
<option value="30">{{ $t("30 days") }}</option>
<option value="60">{{ $t("60 days") }}</option>
<option value="90">{{ $t("90 days") }}</option>
<option value="180">{{ $t("180 days") }}</option>
<option value="365">{{ $t("365 days") }}</option>
</select>
<div class="form-text">
{{ $t("How many days of heartbeat history to show in the status page") }}
</div>
</div>
<div class="my-3">
<label for="switch-theme" class="form-label">{{ $t("Theme") }}</label>
<select id="switch-theme" v-model="config.theme" class="form-select" data-testid="theme-select">
@ -707,6 +722,10 @@ export default {
this.config.domainNameList = [];
}
if (!this.config.heartbeatBarRangeDays) {
this.config.heartbeatBarRangeDays = 90;
}
if (this.config.icon) {
this.imgDataUrl = this.config.icon;
}