From 60d780927d2883919f25dec61e1bd71fd002d7e3 Mon Sep 17 00:00:00 2001 From: Dharun Ashokkumar Date: Tue, 20 Jan 2026 08:37:50 +0530 Subject: [PATCH] feat: add google sheets notification provider for logging monitor events --- .../notification-providers/google-sheets.js | 118 ++++++++++++++++++ server/notification.js | 2 + src/components/notifications/GoogleSheets.vue | 111 ++++++++++++++++ src/components/notifications/index.js | 2 + 4 files changed, 233 insertions(+) create mode 100644 server/notification-providers/google-sheets.js create mode 100644 src/components/notifications/GoogleSheets.vue diff --git a/server/notification-providers/google-sheets.js b/server/notification-providers/google-sheets.js new file mode 100644 index 000000000..41d3e5367 --- /dev/null +++ b/server/notification-providers/google-sheets.js @@ -0,0 +1,118 @@ +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"; + } + + // Prepare row data based on user configuration + let rowData = []; + + if (notification.googleSheetsCustomFormat) { + // Custom format - user defines their own columns + const customColumns = notification.googleSheetsColumns || "timestamp,status,monitor,message"; + const columns = customColumns.split(",").map(col => col.trim()); + + columns.forEach(column => { + switch (column.toLowerCase()) { + case "timestamp": + rowData.push(timestamp); + break; + case "status": + rowData.push(status); + break; + case "monitor": + case "monitorname": + rowData.push(monitorName); + break; + case "url": + case "monitorurl": + rowData.push(monitorUrl); + break; + case "message": + case "msg": + rowData.push(msg); + break; + case "responsetime": + case "ping": + rowData.push(responseTime); + break; + case "statuscode": + rowData.push(statusCode); + break; + default: + rowData.push(""); + } + }); + } else { + // Default format + rowData = [ + timestamp, + status, + monitorName, + monitorUrl, + msg, + responseTime, + statusCode + ]; + } + + // Prepare the request to Google Sheets API + const spreadsheetId = notification.googleSheetsSpreadsheetId; + const sheetName = notification.googleSheetsSheetName || "Sheet1"; + const range = `${sheetName}!A:Z`; + + // Use Google Sheets API v4 to append data + const apiUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${range}:append`; + + const config = this.getAxiosConfigWithProxy({ + params: { + valueInputOption: "USER_ENTERED", + insertDataOption: "INSERT_ROWS" + }, + headers: { + "Authorization": `Bearer ${notification.googleSheetsAccessToken}`, + "Content-Type": "application/json" + } + }); + + const data = { + values: [rowData] + }; + + await axios.post(apiUrl, data, config); + + return okMsg; + } catch (error) { + this.throwGeneralAxiosError(error); + } + } +} + +module.exports = GoogleSheets; diff --git a/server/notification.js b/server/notification.js index de429388c..a1a6fec3c 100644 --- a/server/notification.js +++ b/server/notification.js @@ -17,6 +17,7 @@ const Feishu = require("./notification-providers/feishu"); const Notifery = require("./notification-providers/notifery"); const FreeMobile = require("./notification-providers/freemobile"); const GoogleChat = require("./notification-providers/google-chat"); +const GoogleSheets = require("./notification-providers/google-sheets"); const Gorush = require("./notification-providers/gorush"); const Gotify = require("./notification-providers/gotify"); const GrafanaOncall = require("./notification-providers/grafana-oncall"); @@ -117,6 +118,7 @@ class Notification { new Feishu(), new FreeMobile(), new GoogleChat(), + new GoogleSheets(), new Gorush(), new Gotify(), new GrafanaOncall(), diff --git a/src/components/notifications/GoogleSheets.vue b/src/components/notifications/GoogleSheets.vue new file mode 100644 index 000000000..f4a0d9f8a --- /dev/null +++ b/src/components/notifications/GoogleSheets.vue @@ -0,0 +1,111 @@ + + + diff --git a/src/components/notifications/index.js b/src/components/notifications/index.js index 1a8f6dba3..6a7c3e71b 100644 --- a/src/components/notifications/index.js +++ b/src/components/notifications/index.js @@ -15,6 +15,7 @@ import Elks from "./46elks.vue"; import Feishu from "./Feishu.vue"; import FreeMobile from "./FreeMobile.vue"; import GoogleChat from "./GoogleChat.vue"; +import GoogleSheets from "./GoogleSheets.vue"; import Gorush from "./Gorush.vue"; import Gotify from "./Gotify.vue"; import GrafanaOncall from "./GrafanaOncall.vue"; @@ -105,6 +106,7 @@ const NotificationFormList = { Feishu: Feishu, FreeMobile: FreeMobile, GoogleChat: GoogleChat, + GoogleSheets: GoogleSheets, gorush: Gorush, gotify: Gotify, GrafanaOncall: GrafanaOncall,