feat: add google sheets notification provider

logs monitor events to google spreadsheet via apps script webhook
This commit is contained in:
Dharun Ashokkumar 2026-01-20 09:52:35 +05:30
parent b638ae48ef
commit 491741be23
5 changed files with 185 additions and 1 deletions

View File

@ -0,0 +1,62 @@
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const { DOWN, UP } = require("../../src/util");
class GoogleSheets extends NotificationProvider {
name = "GoogleSheets";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
const okMsg = "Sent Successfully.";
try {
// Prepare the data to be logged
const timestamp = new Date().toISOString();
let status = "N/A";
let monitorName = "N/A";
let monitorUrl = "N/A";
let responseTime = "N/A";
let statusCode = "N/A";
if (monitorJSON) {
monitorName = monitorJSON.name || "N/A";
monitorUrl = this.extractAddress(monitorJSON) || "N/A";
}
if (heartbeatJSON) {
status = heartbeatJSON.status === DOWN ? "DOWN" : heartbeatJSON.status === UP ? "UP" : "UNKNOWN";
responseTime = heartbeatJSON.ping || "N/A";
statusCode = heartbeatJSON.status || "N/A";
}
// Send data to Google Apps Script webhook
const webhookUrl = notification.googleSheetsWebhookUrl;
const config = this.getAxiosConfigWithProxy({
headers: {
"Content-Type": "application/json"
}
});
const data = {
timestamp: timestamp,
status: status,
monitorName: monitorName,
monitorUrl: monitorUrl,
message: msg,
responseTime: responseTime,
statusCode: statusCode
};
await axios.post(webhookUrl, data, config);
return okMsg;
} catch (error) {
this.throwGeneralAxiosError(error);
}
}
}
module.exports = GoogleSheets;

View File

@ -17,6 +17,7 @@ const Feishu = require("./notification-providers/feishu");
const Notifery = require("./notification-providers/notifery"); const Notifery = require("./notification-providers/notifery");
const FreeMobile = require("./notification-providers/freemobile"); const FreeMobile = require("./notification-providers/freemobile");
const GoogleChat = require("./notification-providers/google-chat"); const GoogleChat = require("./notification-providers/google-chat");
const GoogleSheets = require("./notification-providers/google-sheets");
const Gorush = require("./notification-providers/gorush"); const Gorush = require("./notification-providers/gorush");
const Gotify = require("./notification-providers/gotify"); const Gotify = require("./notification-providers/gotify");
const GrafanaOncall = require("./notification-providers/grafana-oncall"); const GrafanaOncall = require("./notification-providers/grafana-oncall");
@ -117,6 +118,7 @@ class Notification {
new Feishu(), new Feishu(),
new FreeMobile(), new FreeMobile(),
new GoogleChat(), new GoogleChat(),
new GoogleSheets(),
new Gorush(), new Gorush(),
new Gotify(), new Gotify(),
new GrafanaOncall(), new GrafanaOncall(),

View File

@ -305,7 +305,9 @@ export default {
}; };
// Other Integrations // Other Integrations
let other = {}; let other = {
GoogleSheets: "Google Sheets",
};
// Regional - Not supported in most regions or documentation is not in English // Regional - Not supported in most regions or documentation is not in English
let regional = { let regional = {

View File

@ -0,0 +1,116 @@
<template>
<div class="mb-3">
<label for="google-sheets-webhook-url" class="form-label">{{ $t("Google Apps Script Webhook URL") }}</label>
<input
id="google-sheets-webhook-url"
v-model="$parent.notification.googleSheetsWebhookUrl"
type="text"
class="form-control"
required
placeholder="https://script.google.com/macros/s/YOUR_SCRIPT_ID/exec"
/>
<div class="form-text">
<p>{{ $t("Deploy a Google Apps Script as a web app and paste the URL here") }}</p>
</div>
</div>
<div class="alert alert-info" style="border-radius: 8px;">
<h6 style="margin-bottom: 12px; font-weight: 600;">{{ $t("Quick Setup Guide") }}:</h6>
<ol style="margin-bottom: 0; padding-left: 20px; line-height: 1.8;">
<li>{{ $t("Open your Google Spreadsheet") }}</li>
<li>{{ $t("Go to Extensions → Apps Script") }}</li>
<li>{{ $t("Paste the script code (see below)") }}</li>
<li>{{ $t("Click Deploy → New deployment → Web app") }}</li>
<li>{{ $t("Set 'Execute as: Me' and 'Who has access: Anyone'") }}</li>
<li>{{ $t("Copy the web app URL and paste it above") }}</li>
</ol>
</div>
<div class="mb-3">
<button
type="button"
class="btn btn-secondary btn-sm"
@click="showScript = !showScript"
>
{{ showScript ? $t("Hide Script Code") : $t("Show Script Code") }}
</button>
</div>
<div v-if="showScript" class="mb-3">
<label class="form-label">{{ $t("Google Apps Script Code") }}:</label>
<textarea
readonly
class="form-control"
rows="15"
style="font-family: monospace; font-size: 12px;"
>function doPost(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = JSON.parse(e.postData.contents);
// Add header row if sheet is empty
if (sheet.getLastRow() === 0) {
sheet.appendRow(['Timestamp', 'Status', 'Monitor Name', 'URL', 'Message', 'Response Time', 'Status Code']);
}
// Add data row
sheet.appendRow([
data.timestamp,
data.status,
data.monitorName,
data.monitorUrl,
data.message,
data.responseTime,
data.statusCode
]);
return ContentService.createTextOutput(JSON.stringify({result: 'success'}))
.setMimeType(ContentService.MimeType.JSON);
}</textarea>
<button
type="button"
class="btn btn-outline-secondary btn-sm mt-2"
@click="copyScript"
>
{{ $t("Copy to Clipboard") }}
</button>
</div>
</template>
<script>
export default {
data() {
return {
showScript: false
};
},
methods: {
copyScript() {
const scriptCode = `function doPost(e) {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var data = JSON.parse(e.postData.contents);
// Add header row if sheet is empty
if (sheet.getLastRow() === 0) {
sheet.appendRow(['Timestamp', 'Status', 'Monitor Name', 'URL', 'Message', 'Response Time', 'Status Code']);
}
// Add data row
sheet.appendRow([
data.timestamp,
data.status,
data.monitorName,
data.monitorUrl,
data.message,
data.responseTime,
data.statusCode
]);
return ContentService.createTextOutput(JSON.stringify({result: 'success'}))
.setMimeType(ContentService.MimeType.JSON);
}`;
navigator.clipboard.writeText(scriptCode);
alert(this.$t("Copied to clipboard!"));
}
}
};
</script>

View File

@ -15,6 +15,7 @@ import Elks from "./46elks.vue";
import Feishu from "./Feishu.vue"; import Feishu from "./Feishu.vue";
import FreeMobile from "./FreeMobile.vue"; import FreeMobile from "./FreeMobile.vue";
import GoogleChat from "./GoogleChat.vue"; import GoogleChat from "./GoogleChat.vue";
import GoogleSheets from "./GoogleSheets.vue";
import Gorush from "./Gorush.vue"; import Gorush from "./Gorush.vue";
import Gotify from "./Gotify.vue"; import Gotify from "./Gotify.vue";
import GrafanaOncall from "./GrafanaOncall.vue"; import GrafanaOncall from "./GrafanaOncall.vue";
@ -105,6 +106,7 @@ const NotificationFormList = {
Feishu: Feishu, Feishu: Feishu,
FreeMobile: FreeMobile, FreeMobile: FreeMobile,
GoogleChat: GoogleChat, GoogleChat: GoogleChat,
GoogleSheets: GoogleSheets,
gorush: Gorush, gorush: Gorush,
gotify: Gotify, gotify: Gotify,
GrafanaOncall: GrafanaOncall, GrafanaOncall: GrafanaOncall,