Add Heii On-Call

This commit is contained in:
Humberto Evans 2024-02-12 15:58:54 -08:00
parent 56aa81e337
commit 93ac212aef
6 changed files with 283 additions and 89 deletions

View file

@ -0,0 +1,68 @@
const { UP, DOWN, getMonitorRelativeURL } = require("../../src/util");
const { setting } = require("../util-server");
const NotificationProvider = require("./notification-provider");
const axios = require("axios");
const heiiOnCallBaseUrl = "http://192.168.2.1:3005";
class HeiiOnCall extends NotificationProvider {
name = "HeiiOnCall";
/**
* @inheritdoc
*/
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
// Payload to Heii On-Call is the entire heartbat JSON
const payload = heartbeatJSON ? heartbeatJSON : {};
if (!heartbeatJSON) {
// Test button was clicked on Notification Setup, trigger the alert as a test
payload["message"] = "Testing UptimeKuma Trigger";
return this.postNotification(notification, "alert", payload);
}
// If we can add url back to mintor to payload
const baseURL = await setting("primaryBaseURL");
if (baseURL && monitorJSON) {
payload["url"] = baseURL + getMonitorRelativeURL(monitorJSON.id);
}
if (heartbeatJSON.status === DOWN) {
// Monitor is DOWN, alert on Heii On-Call
return this.postNotification(notification, "alert", payload);
}
if (heartbeatJSON.status === UP) {
// Monitor is UP, resolve on Heii On-Call
return this.postNotification(notification, "resolve", payload);
}
}
/**
* Post to Heii On-Call
* @param {BeanModel} notification Message title
* @param {string} action Trigger Action (alert, resovle)
* @param {object} payload Data for Heii On-Call
* @returns {Promise<string>} Success message
*/
async postNotification(notification, action, payload) {
const config = {
headers: {
Accept: "application/json",
"Content-Type": "application/json",
Authorization: "Bearer " + notification.heiiOnCallApiKey,
},
};
// Post to Heii On-Call Trigger https://heiioncall.com/docs#manual-triggers
await axios.post(
`${heiiOnCallBaseUrl}/triggers/${notification.heiiOnCallTriggerId}/${action}`,
payload,
config
);
return "Sent Successfully.";
}
}
module.exports = HeiiOnCall;

View file

@ -16,6 +16,7 @@ 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");
const HomeAssistant = require("./notification-providers/home-assistant"); const HomeAssistant = require("./notification-providers/home-assistant");
const HeiiOnCall = require("./notification-providers/heii-oncall");
const Kook = require("./notification-providers/kook"); const Kook = require("./notification-providers/kook");
const Line = require("./notification-providers/line"); const Line = require("./notification-providers/line");
const LineNotify = require("./notification-providers/linenotify"); const LineNotify = require("./notification-providers/linenotify");
@ -56,7 +57,6 @@ const ServerChan = require("./notification-providers/serverchan");
const ZohoCliq = require("./notification-providers/zoho-cliq"); const ZohoCliq = require("./notification-providers/zoho-cliq");
class Notification { class Notification {
providerList = {}; providerList = {};
/** /**
@ -87,6 +87,7 @@ class Notification {
new Gotify(), new Gotify(),
new GrafanaOncall(), new GrafanaOncall(),
new HomeAssistant(), new HomeAssistant(),
new HeiiOnCall(),
new Kook(), new Kook(),
new Line(), new Line(),
new LineNotify(), new LineNotify(),
@ -124,10 +125,10 @@ class Notification {
new Webhook(), new Webhook(),
new WeCom(), new WeCom(),
new GoAlert(), new GoAlert(),
new ZohoCliq() new ZohoCliq(),
]; ];
for (let item of list) { for (let item of list) {
if (! item.name) { if (!item.name) {
throw new Error("Notification provider without name"); throw new Error("Notification provider without name");
} }
@ -147,9 +148,19 @@ class Notification {
* @returns {Promise<string>} Successful msg * @returns {Promise<string>} Successful msg
* @throws Error with fail msg * @throws Error with fail msg
*/ */
static async send(notification, msg, monitorJSON = null, heartbeatJSON = null) { static async send(
notification,
msg,
monitorJSON = null,
heartbeatJSON = null
) {
if (this.providerList[notification.type]) { if (this.providerList[notification.type]) {
return this.providerList[notification.type].send(notification, msg, monitorJSON, heartbeatJSON); return this.providerList[notification.type].send(
notification,
msg,
monitorJSON,
heartbeatJSON
);
} else { } else {
throw new Error("Notification type is not supported"); throw new Error("Notification type is not supported");
} }
@ -171,10 +182,9 @@ class Notification {
userID, userID,
]); ]);
if (! bean) { if (!bean) {
throw new Error("notification not found"); throw new Error("notification not found");
} }
} else { } else {
bean = R.dispense("notification"); bean = R.dispense("notification");
} }
@ -204,7 +214,7 @@ class Notification {
userID, userID,
]); ]);
if (! bean) { if (!bean) {
throw new Error("notification not found"); throw new Error("notification not found");
} }
@ -220,7 +230,6 @@ class Notification {
let exists = commandExistsSync("apprise"); let exists = commandExistsSync("apprise");
return exists; return exists;
} }
} }
/** /**
@ -231,16 +240,17 @@ class Notification {
*/ */
async function applyNotificationEveryMonitor(notificationID, userID) { async function applyNotificationEveryMonitor(notificationID, userID) {
let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [ let monitors = await R.getAll("SELECT id FROM monitor WHERE user_id = ?", [
userID userID,
]); ]);
for (let i = 0; i < monitors.length; i++) { for (let i = 0; i < monitors.length; i++) {
let checkNotification = await R.findOne("monitor_notification", " monitor_id = ? AND notification_id = ? ", [ let checkNotification = await R.findOne(
monitors[i].id, "monitor_notification",
notificationID, " monitor_id = ? AND notification_id = ? ",
]); [monitors[i].id, notificationID]
);
if (! checkNotification) { if (!checkNotification) {
let relation = R.dispense("monitor_notification"); let relation = R.dispense("monitor_notification");
relation.monitor_id = monitors[i].id; relation.monitor_id = monitors[i].id;
relation.notification_id = notificationID; relation.notification_id = notificationID;

View file

@ -1,62 +1,132 @@
<template> <template>
<form @submit.prevent="submit"> <form @submit.prevent="submit">
<div ref="modal" class="modal fade" tabindex="-1" data-bs-backdrop="static"> <div
ref="modal"
class="modal fade"
tabindex="-1"
data-bs-backdrop="static"
>
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h5 id="exampleModalLabel" class="modal-title"> <h5 id="exampleModalLabel" class="modal-title">
{{ $t("Setup Notification") }} {{ $t("Setup Notification") }}
</h5> </h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close" /> <button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"
/>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="mb-3"> <div class="mb-3">
<label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label> <label for="notification-type" class="form-label">{{
<select id="notification-type" v-model="notification.type" class="form-select"> $t("Notification Type")
<option v-for="(name, type) in notificationNameList.regularList" :key="type" :value="type">{{ name }}</option> }}</label>
<select
id="notification-type"
v-model="notification.type"
class="form-select"
>
<option
v-for="(
name, type
) in notificationNameList.regularList"
:key="type"
:value="type"
>
{{ name }}
</option>
<optgroup :label="$t('notificationRegional')"> <optgroup :label="$t('notificationRegional')">
<option v-for="(name, type) in notificationNameList.regionalList" :key="type" :value="type">{{ name }}</option> <option
v-for="(
name, type
) in notificationNameList.regionalList"
:key="type"
:value="type"
>
{{ name }}
</option>
</optgroup> </optgroup>
</select> </select>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label for="notification-name" class="form-label">{{ $t("Friendly Name") }}</label> <label for="notification-name" class="form-label">{{
<input id="notification-name" v-model="notification.name" type="text" class="form-control" required> $t("Friendly Name")
}}</label>
<input
id="notification-name"
v-model="notification.name"
type="text"
class="form-control"
required
/>
</div> </div>
<!-- form body --> <!-- form body -->
<component :is="currentForm" /> <component :is="currentForm" />
<div class="mb-3 mt-4"> <div class="mb-3 mt-4">
<hr class="dropdown-divider mb-4"> <hr class="dropdown-divider mb-4" />
<div class="form-check form-switch"> <div class="form-check form-switch">
<input v-model="notification.isDefault" class="form-check-input" type="checkbox"> <input
<label class="form-check-label">{{ $t("Default enabled") }}</label> v-model="notification.isDefault"
class="form-check-input"
type="checkbox"
/>
<label class="form-check-label">{{
$t("Default enabled")
}}</label>
</div> </div>
<div class="form-text"> <div class="form-text">
{{ $t("enableDefaultNotificationDescription") }} {{ $t("enableDefaultNotificationDescription") }}
</div> </div>
<br> <br />
<div class="form-check form-switch"> <div class="form-check form-switch">
<input v-model="notification.applyExisting" class="form-check-input" type="checkbox"> <input
<label class="form-check-label">{{ $t("Apply on all existing monitors") }}</label> v-model="notification.applyExisting"
class="form-check-input"
type="checkbox"
/>
<label class="form-check-label">{{
$t("Apply on all existing monitors")
}}</label>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button v-if="id" type="button" class="btn btn-danger" :disabled="processing" @click="deleteConfirm"> <button
v-if="id"
type="button"
class="btn btn-danger"
:disabled="processing"
@click="deleteConfirm"
>
{{ $t("Delete") }} {{ $t("Delete") }}
</button> </button>
<button type="button" class="btn btn-warning" :disabled="processing" @click="test"> <button
type="button"
class="btn btn-warning"
:disabled="processing"
@click="test"
>
{{ $t("Test") }} {{ $t("Test") }}
</button> </button>
<button type="submit" class="btn btn-primary" :disabled="processing"> <button
<div v-if="processing" class="spinner-border spinner-border-sm me-1"></div> type="submit"
class="btn btn-primary"
:disabled="processing"
>
<div
v-if="processing"
class="spinner-border spinner-border-sm me-1"
></div>
{{ $t("Save") }} {{ $t("Save") }}
</button> </button>
</div> </div>
@ -65,7 +135,13 @@
</div> </div>
</form> </form>
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteNotification"> <Confirm
ref="confirmDelete"
btn-style="btn-danger"
:yes-text="$t('Yes')"
:no-text="$t('No')"
@yes="deleteNotification"
>
{{ $t("deleteNotificationMsg") }} {{ $t("deleteNotificationMsg") }}
</Confirm> </Confirm>
</template> </template>
@ -121,6 +197,7 @@ export default {
"gotify": "Gotify", "gotify": "Gotify",
"GrafanaOncall": "Grafana Oncall", "GrafanaOncall": "Grafana Oncall",
"HomeAssistant": "Home Assistant", "HomeAssistant": "Home Assistant",
"HeiiOnCall": "Heii On-Call",
"Kook": "Kook", "Kook": "Kook",
"line": "LINE Messenger", "line": "LINE Messenger",
"LineNotify": "LINE Notify", "LineNotify": "LINE Notify",
@ -330,7 +407,8 @@ export default {
@import "../assets/vars.scss"; @import "../assets/vars.scss";
.dark { .dark {
.modal-dialog .form-text, .modal-dialog p { .modal-dialog .form-text,
.modal-dialog p {
color: $dark-font-color; color: $dark-font-color;
} }
} }

View file

@ -0,0 +1,35 @@
<template>
<div class="mb-3">
<label for="heiioncall-apikey" class="form-label"
>{{ $t("API Key")
}}<span style="color: red"><sup>*</sup></span></label
>
<HiddenInput
id="heiioncall-apikey"
v-model="$parent.notification.heiiOnCallApiKey"
required="true"
autocomplete="false"
></HiddenInput>
</div>
<div class="mb-3">
<label for="heiioncall-trigger-id" class="form-label"
>{{ $t("Trigger ID")
}}<span style="color: red"><sup>*</sup></span></label
>
<HiddenInput
id="heiioncall-trigger-id"
v-model="$parent.notification.heiiOnCallTriggerId"
required="true"
autocomplete="false"
></HiddenInput>
</div>
</template>
<script>
import HiddenInput from "../HiddenInput.vue";
export default {
components: {
HiddenInput,
},
};
</script>

View file

@ -14,6 +14,7 @@ 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";
import HomeAssistant from "./HomeAssistant.vue"; import HomeAssistant from "./HomeAssistant.vue";
import HeiiOnCall from "./HeiiOnCall.vue";
import Kook from "./Kook.vue"; import Kook from "./Kook.vue";
import Line from "./Line.vue"; import Line from "./Line.vue";
import LineNotify from "./LineNotify.vue"; import LineNotify from "./LineNotify.vue";
@ -58,60 +59,61 @@ import Splunk from "./Splunk.vue";
* @type { Record<string, any> } * @type { Record<string, any> }
*/ */
const NotificationFormList = { const NotificationFormList = {
"alerta": Alerta, alerta: Alerta,
"AlertNow": AlertNow, AlertNow: AlertNow,
"AliyunSMS": AliyunSMS, AliyunSMS: AliyunSMS,
"apprise": Apprise, apprise: Apprise,
"Bark": Bark, Bark: Bark,
"clicksendsms": ClickSendSMS, clicksendsms: ClickSendSMS,
"smsc": SMSC, smsc: SMSC,
"DingDing": DingDing, DingDing: DingDing,
"discord": Discord, discord: Discord,
"Feishu": Feishu, Feishu: Feishu,
"FreeMobile": FreeMobile, FreeMobile: FreeMobile,
"GoogleChat": GoogleChat, GoogleChat: GoogleChat,
"gorush": Gorush, gorush: Gorush,
"gotify": Gotify, gotify: Gotify,
"GrafanaOncall": GrafanaOncall, GrafanaOncall: GrafanaOncall,
"HomeAssistant": HomeAssistant, HomeAssistant: HomeAssistant,
"Kook": Kook, HeiiOnCall: HeiiOnCall,
"line": Line, Kook: Kook,
"LineNotify": LineNotify, line: Line,
"lunasea": LunaSea, LineNotify: LineNotify,
"matrix": Matrix, lunasea: LunaSea,
"mattermost": Mattermost, matrix: Matrix,
"nostr": Nostr, mattermost: Mattermost,
"ntfy": Ntfy, nostr: Nostr,
"octopush": Octopush, ntfy: Ntfy,
"OneBot": OneBot, octopush: Octopush,
"Opsgenie": Opsgenie, OneBot: OneBot,
"PagerDuty": PagerDuty, Opsgenie: Opsgenie,
"FlashDuty": FlashDuty, PagerDuty: PagerDuty,
"PagerTree": PagerTree, FlashDuty: FlashDuty,
"promosms": PromoSMS, PagerTree: PagerTree,
"pushbullet": Pushbullet, promosms: PromoSMS,
"PushByTechulus": TechulusPush, pushbullet: Pushbullet,
"PushDeer": PushDeer, PushByTechulus: TechulusPush,
"pushover": Pushover, PushDeer: PushDeer,
"pushy": Pushy, pushover: Pushover,
pushy: Pushy,
"rocket.chat": RocketChat, "rocket.chat": RocketChat,
"serwersms": SerwerSMS, serwersms: SerwerSMS,
"signal": Signal, signal: Signal,
"SMSManager": SMSManager, SMSManager: SMSManager,
"slack": Slack, slack: Slack,
"squadcast": Squadcast, squadcast: Squadcast,
"SMSEagle": SMSEagle, SMSEagle: SMSEagle,
"smtp": STMP, smtp: STMP,
"stackfield": Stackfield, stackfield: Stackfield,
"teams": Teams, teams: Teams,
"telegram": Telegram, telegram: Telegram,
"twilio": Twilio, twilio: Twilio,
"Splunk": Splunk, Splunk: Splunk,
"webhook": Webhook, webhook: Webhook,
"WeCom": WeCom, WeCom: WeCom,
"GoAlert": GoAlert, GoAlert: GoAlert,
"ServerChan": ServerChan, ServerChan: ServerChan,
"ZohoCliq": ZohoCliq ZohoCliq: ZohoCliq,
}; };
export default NotificationFormList; export default NotificationFormList;

View file

@ -883,5 +883,6 @@
"deleteRemoteBrowserMessage": "Are you sure want to delete this Remote Browser for all monitors?", "deleteRemoteBrowserMessage": "Are you sure want to delete this Remote Browser for all monitors?",
"GrafanaOncallUrl": "Grafana Oncall URL", "GrafanaOncallUrl": "Grafana Oncall URL",
"Browser Screenshot": "Browser Screenshot", "Browser Screenshot": "Browser Screenshot",
"What is a Remote Browser?": "What is a Remote Browser?" "What is a Remote Browser?": "What is a Remote Browser?",
"Trigger ID": "Trigger ID"
} }