This commit is contained in:
Ponkhy 2021-08-26 02:43:35 +02:00
commit 34ca5c5c15
6 changed files with 171 additions and 31 deletions

View file

@ -4,7 +4,6 @@
"indentation": 4, "indentation": 4,
"no-descending-specificity": null, "no-descending-specificity": null,
"selector-list-comma-newline-after": null, "selector-list-comma-newline-after": null,
"declaration-empty-line-before": null, "declaration-empty-line-before": null
"no-duplicate-selectors": null
} }
} }

View file

@ -279,6 +279,116 @@ class Notification {
throwGeneralAxiosError(error) throwGeneralAxiosError(error)
} }
} else if (notification.type === "mattermost") {
try {
const mattermostUserName = notification.mattermostusername || "Uptime Kuma";
// If heartbeatJSON is null, assume we're testing.
if (heartbeatJSON == null) {
let mattermostTestData = {
username: mattermostUserName,
text: msg,
}
await axios.post(notification.mattermostWebhookUrl, mattermostTestData)
return okMsg;
}
const mattermostChannel = notification.mattermostchannel;
const mattermostIconEmoji = notification.mattermosticonemo;
const mattermostIconUrl = notification.mattermosticonurl;
if (heartbeatJSON["status"] == 0) {
let mattermostdowndata = {
username: mattermostUserName,
text: "Uptime Kuma Alert",
channel: mattermostChannel,
icon_emoji: mattermostIconEmoji,
icon_url: mattermostIconUrl,
attachments: [
{
fallback:
"Your " +
monitorJSON["name"] +
" service went down.",
color: "#FF0000",
title:
"❌ " +
monitorJSON["name"] +
" service went down. ❌",
title_link: monitorJSON["url"],
fields: [
{
short: true,
title: "Service Name",
value: monitorJSON["name"],
},
{
short: true,
title: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
short: false,
title: "Error",
value: heartbeatJSON["msg"],
},
],
},
],
};
await axios.post(
notification.mattermostWebhookUrl,
mattermostdowndata
);
return okMsg;
} else if (heartbeatJSON["status"] == 1) {
let mattermostupdata = {
username: mattermostUserName,
text: "Uptime Kuma Alert",
channel: mattermostChannel,
icon_emoji: mattermostIconEmoji,
icon_url: mattermostIconUrl,
attachments: [
{
fallback:
"Your " +
monitorJSON["name"] +
" service went up!",
color: "#32CD32",
title:
"✅ " +
monitorJSON["name"] +
" service went up! ✅",
title_link: monitorJSON["url"],
fields: [
{
short: true,
title: "Service Name",
value: monitorJSON["name"],
},
{
short: true,
title: "Time (UTC)",
value: heartbeatJSON["time"],
},
{
short: false,
title: "Ping",
value: heartbeatJSON["ping"] + "ms",
},
],
},
],
};
await axios.post(
notification.mattermostWebhookUrl,
mattermostupdata
);
return okMsg;
}
} catch (error) {
throwGeneralAxiosError(error);
}
} else if (notification.type === "pushover") { } else if (notification.type === "pushover") {
let pushoverlink = "https://api.pushover.net/1/messages.json" let pushoverlink = "https://api.pushover.net/1/messages.json"
try { try {

View file

@ -163,10 +163,6 @@ export default {
&.empty { &.empty {
background-color: aliceblue; background-color: aliceblue;
.dark & {
background-color: #d0d3d5;
}
} }
&.down { &.down {

View file

@ -27,6 +27,7 @@
<option value="apprise">Apprise (Support 50+ Notification services)</option> <option value="apprise">Apprise (Support 50+ Notification services)</option>
<option value="pushbullet">Pushbullet</option> <option value="pushbullet">Pushbullet</option>
<option value="line">Line Messenger</option> <option value="line">Line Messenger</option>
<option value="mattermost">Mattermost</option>
</select> </select>
</div> </div>
@ -238,6 +239,39 @@
</div> </div>
</template> </template>
<template v-if="notification.type === 'mattermost'">
<div class="mb-3">
<label for="mattermost-webhook-url" class="form-label">Webhook URL<span style="color:red;"><sup>*</sup></span></label>
<input id="mattermost-webhook-url" v-model="notification.mattermostWebhookUrl" type="text" class="form-control" required>
<label for="mattermost-username" class="form-label">Username</label>
<input id="mattermost-username" v-model="notification.mattermostusername" type="text" class="form-control">
<label for="mattermost-iconurl" class="form-label">Icon URL</label>
<input id="mattermost-iconurl" v-model="notification.mattermosticonurl" type="text" class="form-control">
<label for="mattermost-iconemo" class="form-label">Icon Emoji</label>
<input id="mattermost-iconemo" v-model="notification.mattermosticonemo" type="text" class="form-control">
<label for="mattermost-channel" class="form-label">Channel Name</label>
<input id="mattermost-channel-name" v-model="notification.mattermostchannel" type="text" class="form-control">
<div class="form-text">
<span style="color:red;"><sup>*</sup></span>Required
<p style="margin-top: 8px;">
More info about webhooks on: <a href="https://docs.mattermost.com/developer/webhooks-incoming.html" target="_blank">https://docs.mattermost.com/developer/webhooks-incoming.html</a>
</p>
<p style="margin-top: 8px;">
You can override the default channel that webhook posts to by entering the channel name into "Channel Name" field. This needs to be enabled in Mattermost webhook settings. Ex: #other-channel
</p>
<p style="margin-top: 8px;">
If you leave the Uptime Kuma URL field blank, it will default to the Project Github page.
</p>
<p style="margin-top: 8px;">
You can provide a link to a picture in "Icon URL" to override the default profile picture. Will not be used if Icon Emoji is set.
</p>
<p style="margin-top: 8px;">
Emoji cheat sheet: <a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a> Note: emoji takes preference over Icon URL.
</p>
</div>
</div>
</template>
<template v-if="notification.type === 'pushy'"> <template v-if="notification.type === 'pushy'">
<div class="mb-3"> <div class="mb-3">
<label for="pushy-app-token" class="form-label">API_KEY</label> <label for="pushy-app-token" class="form-label">API_KEY</label>

View file

@ -24,6 +24,7 @@ export default {
}, },
data() { data() {
return { return {
// Configurable filtering on top of the returned data
chartPeriodHrs: 6, chartPeriodHrs: 6,
}; };
}, },
@ -55,11 +56,10 @@ export default {
elements: { elements: {
point: { point: {
// Hide points on chart unless mouse-over
radius: 0, radius: 0,
hitRadius: 100,
}, },
bar: {
barThickness: "flex",
}
}, },
scales: { scales: {
x: { x: {
@ -77,9 +77,9 @@ export default {
maxRotation: 0, maxRotation: 0,
autoSkipPadding: 30, autoSkipPadding: 30,
}, },
bounds: "ticks",
grid: { grid: {
color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)", color: this.$root.theme === "light" ? "rgba(0,0,0,0.1)" : "rgba(255,255,255,0.1)",
offset: false,
}, },
}, },
y: { y: {
@ -109,8 +109,11 @@ export default {
mode: "nearest", mode: "nearest",
intersect: false, intersect: false,
padding: 10, padding: 10,
backgroundColor: this.$root.theme === "light" ? "rgba(212,232,222,1.0)" : "rgba(32,42,38,1.0)",
bodyColor: this.$root.theme === "light" ? "rgba(12,12,18,1.0)" : "rgba(220,220,220,1.0)",
titleColor: this.$root.theme === "light" ? "rgba(12,12,18,1.0)" : "rgba(220,220,220,1.0)",
filter: function (tooltipItem) { filter: function (tooltipItem) {
return tooltipItem.datasetIndex === 0; return tooltipItem.datasetIndex === 0; // Hide tooltip on Bar Chart
}, },
callbacks: { callbacks: {
label: (context) => { label: (context) => {
@ -125,32 +128,29 @@ export default {
} }
}, },
chartData() { chartData() {
let ping_data = []; let pingData = []; // Ping Data for Line Chart, y-axis contains ping time
let down_data = []; let downData = []; // Down Data for Bar Chart, y-axis is 1 if target is down, 0 if target is up
if (this.monitorId in this.$root.heartbeatList) { if (this.monitorId in this.$root.heartbeatList) {
ping_data = this.$root.heartbeatList[this.monitorId] this.$root.heartbeatList[this.monitorId]
.filter( .filter(
(beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours"))) (beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours")))
.map((beat) => { .map((beat) => {
return { const x = this.$root.datetime(beat.time);
x: dayjs.utc(beat.time).tz(this.$root.timezone).format("YYYY-MM-DD HH:mm:ss"), pingData.push({
x,
y: beat.ping, y: beat.ping,
};
}); });
down_data = this.$root.heartbeatList[this.monitorId] downData.push({
.filter( x,
(beat) => dayjs.utc(beat.time).tz(this.$root.timezone).isAfter(dayjs().subtract(this.chartPeriodHrs, "hours")))
.map((beat) => {
return {
x: dayjs.utc(beat.time).tz(this.$root.timezone).format("YYYY-MM-DD HH:mm:ss"),
y: beat.status === 0 ? 1 : 0, y: beat.status === 0 ? 1 : 0,
}; })
}); });
} }
return { return {
datasets: [ datasets: [
{ {
data: ping_data, // Line Chart
data: pingData,
fill: "origin", fill: "origin",
tension: 0.2, tension: 0.2,
borderColor: "#5CDD8B", borderColor: "#5CDD8B",
@ -158,11 +158,15 @@ export default {
yAxisID: "y", yAxisID: "y",
}, },
{ {
// Bar Chart
type: "bar", type: "bar",
data: down_data, data: downData,
borderColor: "#00000000", borderColor: "#00000000",
backgroundColor: "#DC354568", backgroundColor: "#DC354568",
yAxisID: "y1", yAxisID: "y1",
barThickness: "flex",
barPercentage: 1,
categoryPercentage: 1,
}, },
], ],
}; };

View file

@ -191,6 +191,7 @@ main {
} }
footer { footer {
color: #aaa;
font-size: 13px; font-size: 13px;
margin-top: 10px; margin-top: 10px;
padding-bottom: 30px; padding-bottom: 30px;
@ -198,10 +199,6 @@ footer {
text-align: center; text-align: center;
} }
footer {
color: #aaa;
}
.dark { .dark {
header { header {
background-color: #161b22; background-color: #161b22;