Skip to content
Closed
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
13 changes: 13 additions & 0 deletions db/knex_migrations/2025-10-15-0000-add-response-to-heartbeat.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
exports.up = function (knex) {
return knex.schema
.alterTable("heartbeat", function (table) {
table.text("response").nullable().defaultTo(null);
});
};

exports.down = function (knex) {
return knex.schema
.alterTable("heartbeat", function (table) {
table.dropColumn("response");
});
};
15 changes: 15 additions & 0 deletions db/knex_migrations/2025-10-15-0001-add-monitor-response-config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
exports.up = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.boolean("save_response").notNullable().defaultTo(false);
table.integer("response_max_length").defaultTo(10240); // Default 10KB
});
};

exports.down = function (knex) {
return knex.schema
.alterTable("monitor", function (table) {
table.dropColumn("save_response");
table.dropColumn("response_max_length");
});
};
1 change: 1 addition & 0 deletions server/model/heartbeat.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class Heartbeat extends BeanModel {
important: this._important,
duration: this._duration,
retries: this._retries,
response: this._response,
};
}

Expand Down
27 changes: 27 additions & 0 deletions server/model/monitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,10 @@ class Monitor extends BeanModel {
ping_numeric: this.isPingNumeric(),
ping_count: this.ping_count,
ping_per_request_timeout: this.ping_per_request_timeout,

// response saving options
saveResponse: this.getSaveResponse(),
responseMaxLength: this.response_max_length ?? 10240,
};

if (includeSensitiveData) {
Expand Down Expand Up @@ -340,6 +344,14 @@ class Monitor extends BeanModel {
return Boolean(this.kafkaProducerAllowAutoTopicCreation);
}

/**
* Parse to boolean
* @returns {boolean} Should save response data?
*/
getSaveResponse() {
return Boolean(this.save_response);
}

/**
* Start monitor
* @param {Server} io Socket server instance
Expand Down Expand Up @@ -565,6 +577,21 @@ class Monitor extends BeanModel {
bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime;

// Store response data for notifications (with size limit) if enabled
if (this.getSaveResponse() && res.data) {
let responseData = res.data;
// Convert to string if not already
if (typeof responseData !== "string") {
responseData = JSON.stringify(responseData);
}
// Use configured max length: 0 = unlimited, otherwise default to 10KB
const maxSize = this.response_max_length !== undefined ? this.response_max_length : 10240;
if (maxSize > 0 && responseData.length > maxSize) {
responseData = responseData.substring(0, maxSize) + "... (truncated)";
}
bean.response = responseData;
}

// fallback for if kelog event is not emitted, but we may still have tlsInfo,
// e.g. if the connection is made through a proxy
if (this.getUrl()?.protocol === "https:" && tlsInfo.valid === undefined) {
Expand Down
2 changes: 2 additions & 0 deletions server/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -837,6 +837,8 @@ let needSetup = false;
bean.packetSize = monitor.packetSize;
bean.maxredirects = monitor.maxredirects;
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
bean.save_response = monitor.saveResponse;
bean.response_max_length = monitor.responseMaxLength;
bean.dns_resolve_type = monitor.dns_resolve_type;
bean.dns_resolve_server = monitor.dns_resolve_server;
bean.pushToken = monitor.pushToken;
Expand Down
4 changes: 4 additions & 0 deletions src/lang/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,10 @@
"maxRedirectDescription": "Maximum number of redirects to follow. Set to 0 to disable redirects.",
"Upside Down Mode": "Upside Down Mode",
"Max. Redirects": "Max. Redirects",
"saveResponseForNotifications": "Save HTTP Response for Notifications",
"saveResponseDescription": "Stores the HTTP response and makes it available to notification templates as {templateVariable}",
"responseMaxLength": "Response Max Length (bytes)",
"responseMaxLengthDescription": "Maximum size of response data to store. Set to 0 for unlimited. Larger responses will be truncated. Default: 10240 (10KB)",
"Accepted Status Codes": "Accepted Status Codes",
"Push URL": "Push URL",
"needPushEvery": "You should call this URL every {0} seconds.",
Expand Down
28 changes: 27 additions & 1 deletion src/pages/EditMonitor.vue
Original file line number Diff line number Diff line change
Expand Up @@ -755,7 +755,7 @@
</div>

<!-- HTTP / Keyword only -->
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query' || monitor.type === 'grpc-keyword' ">
<template v-if="monitor.type === 'http' || monitor.type === 'keyword' || monitor.type === 'json-query'">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why this change?

<div class="my-3">
<label for="maxRedirects" class="form-label">{{ $t("Max. Redirects") }}</label>
<input id="maxRedirects" v-model="monitor.maxredirects" type="number" class="form-control" required min="0" step="1">
Expand All @@ -764,6 +764,30 @@
</div>
</div>

<div class="my-3">
<div class="form-check">
<input id="saveResponse" v-model="monitor.saveResponse" class="form-check-input" type="checkbox">
<label class="form-check-label" for="saveResponse">
{{ $t("saveResponseForNotifications") }}
</label>
</div>
<div class="form-text">
<i18n-t keypath="saveResponseDescription" tag="div" class="form-text">
<template #templateVariable>
<code>heartbeatJSON.response</code>
</template>
</i18n-t>
</div>
</div>

<div v-if="monitor.saveResponse" class="my-3">
<label for="responseMaxLength" class="form-label">{{ $t("responseMaxLength") }}</label>
<input id="responseMaxLength" v-model="monitor.responseMaxLength" type="number" class="form-control" required min="0" step="1">
<div class="form-text">
{{ $t("responseMaxLengthDescription") }}
</div>
</div>

<div class="my-3">
<label for="acceptedStatusCodes" class="form-label">{{ $t("Accepted Status Codes") }}</label>

Expand Down Expand Up @@ -1199,6 +1223,8 @@ const monitorDefaults = {
expiryNotification: false,
maxredirects: 10,
accepted_statuscodes: [ "200-299" ],
saveResponse: false,
responseMaxLength: 10240,
dns_resolve_type: "A",
dns_resolve_server: "1.1.1.1",
docker_container: "",
Expand Down
Loading