mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-05-20 06:02:33 +02:00
Merge branch 'master' into master
This commit is contained in:
commit
98474396f8
22 changed files with 371 additions and 14 deletions
13
db/knex_migrations/2025-05-09-0000-add-custom-url.js
Normal file
13
db/knex_migrations/2025-05-09-0000-add-custom-url.js
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
// Add column custom_url to monitor_group table
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return knex.schema
|
||||||
|
.alterTable("monitor_group", function (table) {
|
||||||
|
table.text("custom_url", "text");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (knex) {
|
||||||
|
return knex.schema.alterTable("monitor_group", function (table) {
|
||||||
|
table.dropColumn("custom_url");
|
||||||
|
});
|
||||||
|
};
|
|
@ -33,7 +33,7 @@ class Group extends BeanModel {
|
||||||
*/
|
*/
|
||||||
async getMonitorList() {
|
async getMonitorList() {
|
||||||
return R.convertToBeans("monitor", await R.getAll(`
|
return R.convertToBeans("monitor", await R.getAll(`
|
||||||
SELECT monitor.*, monitor_group.send_url FROM monitor, monitor_group
|
SELECT monitor.*, monitor_group.send_url, monitor_group.custom_url FROM monitor, monitor_group
|
||||||
WHERE monitor.id = monitor_group.monitor_id
|
WHERE monitor.id = monitor_group.monitor_id
|
||||||
AND group_id = ?
|
AND group_id = ?
|
||||||
ORDER BY monitor_group.weight
|
ORDER BY monitor_group.weight
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Monitor extends BeanModel {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.sendUrl) {
|
if (this.sendUrl) {
|
||||||
obj.url = this.url;
|
obj.url = this.customUrl ?? this.url;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showTags) {
|
if (showTags) {
|
||||||
|
|
|
@ -46,10 +46,10 @@ class Discord extends NotificationProvider {
|
||||||
name: "Service Name",
|
name: "Service Name",
|
||||||
value: monitorJSON["name"],
|
value: monitorJSON["name"],
|
||||||
},
|
},
|
||||||
{
|
...(!notification.disableUrl ? [{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
value: this.extractAddress(monitorJSON),
|
value: this.extractAddress(monitorJSON),
|
||||||
},
|
}] : []),
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
value: heartbeatJSON["localDateTime"],
|
value: heartbeatJSON["localDateTime"],
|
||||||
|
@ -83,10 +83,10 @@ class Discord extends NotificationProvider {
|
||||||
name: "Service Name",
|
name: "Service Name",
|
||||||
value: monitorJSON["name"],
|
value: monitorJSON["name"],
|
||||||
},
|
},
|
||||||
{
|
...(!notification.disableUrl ? [{
|
||||||
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
name: monitorJSON["type"] === "push" ? "Service Type" : "Service URL",
|
||||||
value: this.extractAddress(monitorJSON),
|
value: this.extractAddress(monitorJSON),
|
||||||
},
|
}] : []),
|
||||||
{
|
{
|
||||||
name: `Time (${heartbeatJSON["timezone"]})`,
|
name: `Time (${heartbeatJSON["timezone"]})`,
|
||||||
value: heartbeatJSON["localDateTime"],
|
value: heartbeatJSON["localDateTime"],
|
||||||
|
|
53
server/notification-providers/notifery.js
Normal file
53
server/notification-providers/notifery.js
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
const { getMonitorRelativeURL, UP } = require("../../src/util");
|
||||||
|
const { setting } = require("../util-server");
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
|
||||||
|
class Notifery extends NotificationProvider {
|
||||||
|
name = "notifery";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
const okMsg = "Sent Successfully.";
|
||||||
|
const url = "https://api.notifery.com/event";
|
||||||
|
|
||||||
|
let data = {
|
||||||
|
title: notification.notiferyTitle || "Uptime Kuma Alert",
|
||||||
|
message: msg,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (notification.notiferyGroup) {
|
||||||
|
data.group = notification.notiferyGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link to the monitor
|
||||||
|
const baseURL = await setting("primaryBaseURL");
|
||||||
|
if (baseURL && monitorJSON) {
|
||||||
|
data.message += `\n\nMonitor: ${baseURL}${getMonitorRelativeURL(monitorJSON.id)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (heartbeatJSON) {
|
||||||
|
data.code = heartbeatJSON.status === UP ? 0 : 1;
|
||||||
|
|
||||||
|
if (heartbeatJSON.ping) {
|
||||||
|
data.duration = heartbeatJSON.ping;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"x-api-key": notification.notiferyApiKey,
|
||||||
|
};
|
||||||
|
|
||||||
|
await axios.post(url, data, { headers });
|
||||||
|
return okMsg;
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Notifery;
|
|
@ -145,6 +145,7 @@ class Slack extends NotificationProvider {
|
||||||
|
|
||||||
const title = "Uptime Kuma Alert";
|
const title = "Uptime Kuma Alert";
|
||||||
let data = {
|
let data = {
|
||||||
|
"text": msg,
|
||||||
"channel": notification.slackchannel,
|
"channel": notification.slackchannel,
|
||||||
"username": notification.slackusername,
|
"username": notification.slackusername,
|
||||||
"icon_emoji": notification.slackiconemo,
|
"icon_emoji": notification.slackiconemo,
|
||||||
|
|
|
@ -42,6 +42,7 @@ class SMTP extends NotificationProvider {
|
||||||
// default values in case the user does not want to template
|
// default values in case the user does not want to template
|
||||||
let subject = msg;
|
let subject = msg;
|
||||||
let body = msg;
|
let body = msg;
|
||||||
|
let useHTMLBody = false;
|
||||||
if (heartbeatJSON) {
|
if (heartbeatJSON) {
|
||||||
body = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
body = `${msg}\nTime (${heartbeatJSON["timezone"]}): ${heartbeatJSON["localDateTime"]}`;
|
||||||
}
|
}
|
||||||
|
@ -50,11 +51,11 @@ class SMTP extends NotificationProvider {
|
||||||
// cannot end with whitespace as this often raises spam scores
|
// cannot end with whitespace as this often raises spam scores
|
||||||
const customSubject = notification.customSubject?.trim() || "";
|
const customSubject = notification.customSubject?.trim() || "";
|
||||||
const customBody = notification.customBody?.trim() || "";
|
const customBody = notification.customBody?.trim() || "";
|
||||||
|
|
||||||
if (customSubject !== "") {
|
if (customSubject !== "") {
|
||||||
subject = await this.renderTemplate(customSubject, msg, monitorJSON, heartbeatJSON);
|
subject = await this.renderTemplate(customSubject, msg, monitorJSON, heartbeatJSON);
|
||||||
}
|
}
|
||||||
if (customBody !== "") {
|
if (customBody !== "") {
|
||||||
|
useHTMLBody = notification.htmlBody || false;
|
||||||
body = await this.renderTemplate(customBody, msg, monitorJSON, heartbeatJSON);
|
body = await this.renderTemplate(customBody, msg, monitorJSON, heartbeatJSON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -67,7 +68,8 @@ class SMTP extends NotificationProvider {
|
||||||
bcc: notification.smtpBCC,
|
bcc: notification.smtpBCC,
|
||||||
to: notification.smtpTo,
|
to: notification.smtpTo,
|
||||||
subject: subject,
|
subject: subject,
|
||||||
text: body,
|
// If the email body is custom, and the user wants it, set the email body as HTML
|
||||||
|
[useHTMLBody ? "html" : "text"]: body
|
||||||
});
|
});
|
||||||
|
|
||||||
return okMsg;
|
return okMsg;
|
||||||
|
|
37
server/notification-providers/spugpush.js
Normal file
37
server/notification-providers/spugpush.js
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
const NotificationProvider = require("./notification-provider");
|
||||||
|
const axios = require("axios");
|
||||||
|
const { DOWN, UP } = require("../../src/util");
|
||||||
|
|
||||||
|
class SpugPush extends NotificationProvider {
|
||||||
|
|
||||||
|
name = "SpugPush";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async send(notification, msg, monitorJSON = null, heartbeatJSON = null) {
|
||||||
|
let okMsg = "Sent Successfully.";
|
||||||
|
try {
|
||||||
|
let formData = {
|
||||||
|
title: "Uptime Kuma Message",
|
||||||
|
content: msg
|
||||||
|
};
|
||||||
|
if (heartbeatJSON) {
|
||||||
|
if (heartbeatJSON["status"] === UP) {
|
||||||
|
formData.title = `UptimeKuma 「${monitorJSON["name"]}」 is Up`;
|
||||||
|
formData.content = `[✅ Up] ${heartbeatJSON["msg"]}`;
|
||||||
|
} else if (heartbeatJSON["status"] === DOWN) {
|
||||||
|
formData.title = `UptimeKuma 「${monitorJSON["name"]}」 is Down`;
|
||||||
|
formData.content = `[🔴 Down] ${heartbeatJSON["msg"]}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const apiUrl = `https://push.spug.cc/send/${notification.templateKey}`;
|
||||||
|
await axios.post(apiUrl, formData);
|
||||||
|
return okMsg;
|
||||||
|
} catch (error) {
|
||||||
|
this.throwGeneralAxiosError(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SpugPush;
|
|
@ -13,6 +13,7 @@ const DingDing = require("./notification-providers/dingding");
|
||||||
const Discord = require("./notification-providers/discord");
|
const Discord = require("./notification-providers/discord");
|
||||||
const Elks = require("./notification-providers/46elks");
|
const Elks = require("./notification-providers/46elks");
|
||||||
const Feishu = require("./notification-providers/feishu");
|
const Feishu = require("./notification-providers/feishu");
|
||||||
|
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 Gorush = require("./notification-providers/gorush");
|
const Gorush = require("./notification-providers/gorush");
|
||||||
|
@ -75,6 +76,7 @@ const Wpush = require("./notification-providers/wpush");
|
||||||
const SendGrid = require("./notification-providers/send-grid");
|
const SendGrid = require("./notification-providers/send-grid");
|
||||||
const YZJ = require("./notification-providers/yzj");
|
const YZJ = require("./notification-providers/yzj");
|
||||||
const SMSPlanet = require("./notification-providers/sms-planet");
|
const SMSPlanet = require("./notification-providers/sms-planet");
|
||||||
|
const SpugPush = require("./notification-providers/spugpush");
|
||||||
|
|
||||||
class Notification {
|
class Notification {
|
||||||
|
|
||||||
|
@ -167,6 +169,8 @@ class Notification {
|
||||||
new SendGrid(),
|
new SendGrid(),
|
||||||
new YZJ(),
|
new YZJ(),
|
||||||
new SMSPlanet(),
|
new SMSPlanet(),
|
||||||
|
new SpugPush(),
|
||||||
|
new Notifery(),
|
||||||
];
|
];
|
||||||
for (let item of list) {
|
for (let item of list) {
|
||||||
if (! item.name) {
|
if (! item.name) {
|
||||||
|
|
|
@ -211,6 +211,10 @@ module.exports.statusPageSocketHandler = (socket) => {
|
||||||
relationBean.send_url = monitor.sendUrl;
|
relationBean.send_url = monitor.sendUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (monitor.url !== undefined) {
|
||||||
|
relationBean.custom_url = monitor.url;
|
||||||
|
}
|
||||||
|
|
||||||
await R.store(relationBean);
|
await R.store(relationBean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<div class="my-3 form-check">
|
<div class="my-3 form-check">
|
||||||
<input id="show-clickable-link" v-model="monitor.isClickAble" class="form-check-input" type="checkbox" @click="toggleLink(monitor.group_index, monitor.monitor_index)" />
|
<input id="show-clickable-link" v-model="monitor.isClickAble" class="form-check-input" type="checkbox" data-testid="show-clickable-link" @click="toggleLink(monitor.group_index, monitor.monitor_index)" />
|
||||||
<label class="form-check-label" for="show-clickable-link">
|
<label class="form-check-label" for="show-clickable-link">
|
||||||
{{ $t("Show Clickable Link") }}
|
{{ $t("Show Clickable Link") }}
|
||||||
</label>
|
</label>
|
||||||
|
@ -19,6 +19,16 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Custom URL -->
|
||||||
|
<template v-if="monitor.isClickAble">
|
||||||
|
<label for="customUrl" class="form-label">{{ $t("Custom URL") }}</label>
|
||||||
|
<input id="customUrl" :value="monitor.url" type="url" class="form-control" data-testid="custom-url-input" @input="e => changeUrl(monitor.group_index, monitor.monitor_index, e.target!.value)">
|
||||||
|
|
||||||
|
<div class="form-text mb-3">
|
||||||
|
{{ $t("customUrlDescription") }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
class="btn btn-primary btn-add-group me-2"
|
class="btn btn-primary btn-add-group me-2"
|
||||||
@click="$refs.badgeGeneratorDialog.show(monitor.id, monitor.name)"
|
@click="$refs.badgeGeneratorDialog.show(monitor.id, monitor.name)"
|
||||||
|
@ -29,7 +39,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="modal-footer">
|
<div class="modal-footer">
|
||||||
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal">
|
<button type="submit" class="btn btn-danger" data-bs-dismiss="modal" data-testid="monitor-settings-close">
|
||||||
{{ $t("Close") }}
|
{{ $t("Close") }}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
@ -78,6 +88,7 @@ export default {
|
||||||
monitor_index: monitor.index,
|
monitor_index: monitor.index,
|
||||||
group_index: group.index,
|
group_index: group.index,
|
||||||
isClickAble: this.showLink(monitor),
|
isClickAble: this.showLink(monitor),
|
||||||
|
url: monitor.element.url,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.MonitorSettingDialog.show();
|
this.MonitorSettingDialog.show();
|
||||||
|
@ -110,6 +121,17 @@ export default {
|
||||||
}
|
}
|
||||||
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
|
return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle the value of sendUrl
|
||||||
|
* @param {number} groupIndex Index of group monitor is member of
|
||||||
|
* @param {number} index Index of monitor within group
|
||||||
|
* @param {string} value The new value of the url
|
||||||
|
* @returns {void}
|
||||||
|
*/
|
||||||
|
changeUrl(groupIndex, index, value) {
|
||||||
|
this.$root.publicGroupList[groupIndex].monitorList[index].url = value;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -168,7 +168,8 @@ export default {
|
||||||
"waha": "WhatsApp (WAHA)",
|
"waha": "WhatsApp (WAHA)",
|
||||||
"gtxmessaging": "GtxMessaging",
|
"gtxmessaging": "GtxMessaging",
|
||||||
"Cellsynt": "Cellsynt",
|
"Cellsynt": "Cellsynt",
|
||||||
"SendGrid": "SendGrid"
|
"SendGrid": "SendGrid",
|
||||||
|
"notifery": "Notifery"
|
||||||
};
|
};
|
||||||
|
|
||||||
// Put notifications here if it's not supported in most regions or its documentation is not in English
|
// Put notifications here if it's not supported in most regions or its documentation is not in English
|
||||||
|
@ -185,6 +186,7 @@ export default {
|
||||||
"WeCom": "WeCom (企业微信群机器人)",
|
"WeCom": "WeCom (企业微信群机器人)",
|
||||||
"ServerChan": "ServerChan (Server酱)",
|
"ServerChan": "ServerChan (Server酱)",
|
||||||
"PushPlus": "PushPlus (推送加)",
|
"PushPlus": "PushPlus (推送加)",
|
||||||
|
"SpugPush": "SpugPush(Spug推送助手)",
|
||||||
"smsc": "SMSC",
|
"smsc": "SMSC",
|
||||||
"WPush": "WPush(wpush.cn)",
|
"WPush": "WPush(wpush.cn)",
|
||||||
"YZJ": "YZJ (云之家自定义机器人)",
|
"YZJ": "YZJ (云之家自定义机器人)",
|
||||||
|
|
|
@ -58,6 +58,7 @@
|
||||||
v-if="editMode"
|
v-if="editMode"
|
||||||
:class="{'link-active': true, 'btn-link': true}"
|
:class="{'link-active': true, 'btn-link': true}"
|
||||||
icon="cog" class="action me-3"
|
icon="cog" class="action me-3"
|
||||||
|
data-testid="monitor-settings"
|
||||||
@click="$refs.monitorSettingDialog.show(group, monitor)"
|
@click="$refs.monitorSettingDialog.show(group, monitor)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -53,6 +53,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input id="discord-disable-url" v-model="$parent.notification.disableUrl" class="form-check-input" type="checkbox" role="switch">
|
||||||
|
<label class="form-check-label" for="discord-disable-url">{{ $t("Disable URL in Notification") }}</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
|
@ -60,6 +67,9 @@ export default {
|
||||||
if (!this.$parent.notification.discordChannelType) {
|
if (!this.$parent.notification.discordChannelType) {
|
||||||
this.$parent.notification.discordChannelType = "channel";
|
this.$parent.notification.discordChannelType = "channel";
|
||||||
}
|
}
|
||||||
|
if (this.$parent.notification.disableUrl === undefined) {
|
||||||
|
this.$parent.notification.disableUrl = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
49
src/components/notifications/Notifery.vue
Normal file
49
src/components/notifications/Notifery.vue
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="notifery-api-key" class="form-label">{{
|
||||||
|
$t("API Key")
|
||||||
|
}}</label>
|
||||||
|
<HiddenInput
|
||||||
|
id="notifery-api-key"
|
||||||
|
v-model="$parent.notification.notiferyApiKey"
|
||||||
|
:required="true"
|
||||||
|
autocomplete="new-password"
|
||||||
|
></HiddenInput>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="notifery-title" class="form-label">{{ $t("Title") }}</label>
|
||||||
|
<input
|
||||||
|
id="notifery-title"
|
||||||
|
v-model="$parent.notification.notiferyTitle"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
placeholder="Uptime Kuma Alert"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="notifery-group" class="form-label">{{ $t("Group") }}</label>
|
||||||
|
<input
|
||||||
|
id="notifery-group"
|
||||||
|
v-model="$parent.notification.notiferyGroup"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
:placeholder="$t('Optional')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||||
|
<a href="https://docs.notifery.com/api/event/" target="_blank">https://docs.notifery.com/api/event/</a>
|
||||||
|
</i18n-t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HiddenInput from "../HiddenInput.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
HiddenInput,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -79,6 +79,15 @@
|
||||||
<div class="form-text">{{ $t("leave blank for default body") }}</div>
|
<div class="form-text">{{ $t("leave blank for default body") }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<div class="form-check">
|
||||||
|
<input id="use-html-body" v-model="$parent.notification.htmlBody" class="form-check-input" type="checkbox" value="">
|
||||||
|
<label class="form-check-label" for="use-html-body">
|
||||||
|
{{ $t("Use HTML for custom E-mail body") }}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ToggleSection :heading="$t('smtpDkimSettings')">
|
<ToggleSection :heading="$t('smtpDkimSettings')">
|
||||||
<i18n-t tag="div" keypath="smtpDkimDesc" class="form-text mb-3">
|
<i18n-t tag="div" keypath="smtpDkimDesc" class="form-text mb-3">
|
||||||
<a href="https://nodemailer.com/dkim/" target="_blank">{{ $t("documentation") }}</a>
|
<a href="https://nodemailer.com/dkim/" target="_blank">{{ $t("documentation") }}</a>
|
||||||
|
|
19
src/components/notifications/SpugPush.vue
Normal file
19
src/components/notifications/SpugPush.vue
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<template>
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="spugpush-templateKey" class="form-label">{{ $t("SpugPush Template Code") }}</label>
|
||||||
|
<HiddenInput id="spugpush-templateKey" v-model="$parent.notification.templateKey" :required="true" autocomplete="new-password"></HiddenInput>
|
||||||
|
</div>
|
||||||
|
<i18n-t tag="p" keypath="More info on:" style="margin-top: 8px;">
|
||||||
|
<a href="https://push.spug.cc/guide/plugin/kuma" rel="noopener noreferrer" target="_blank">https://push.spug.cc</a>
|
||||||
|
</i18n-t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import HiddenInput from "../HiddenInput.vue";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
HiddenInput,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -4,6 +4,7 @@ import AliyunSMS from "./AliyunSms.vue";
|
||||||
import Apprise from "./Apprise.vue";
|
import Apprise from "./Apprise.vue";
|
||||||
import Bark from "./Bark.vue";
|
import Bark from "./Bark.vue";
|
||||||
import Bitrix24 from "./Bitrix24.vue";
|
import Bitrix24 from "./Bitrix24.vue";
|
||||||
|
import Notifery from "./Notifery.vue";
|
||||||
import ClickSendSMS from "./ClickSendSMS.vue";
|
import ClickSendSMS from "./ClickSendSMS.vue";
|
||||||
import CallMeBot from "./CallMeBot.vue";
|
import CallMeBot from "./CallMeBot.vue";
|
||||||
import SMSC from "./SMSC.vue";
|
import SMSC from "./SMSC.vue";
|
||||||
|
@ -64,6 +65,7 @@ import WeCom from "./WeCom.vue";
|
||||||
import GoAlert from "./GoAlert.vue";
|
import GoAlert from "./GoAlert.vue";
|
||||||
import ZohoCliq from "./ZohoCliq.vue";
|
import ZohoCliq from "./ZohoCliq.vue";
|
||||||
import Splunk from "./Splunk.vue";
|
import Splunk from "./Splunk.vue";
|
||||||
|
import SpugPush from "./SpugPush.vue";
|
||||||
import SevenIO from "./SevenIO.vue";
|
import SevenIO from "./SevenIO.vue";
|
||||||
import Whapi from "./Whapi.vue";
|
import Whapi from "./Whapi.vue";
|
||||||
import WAHA from "./WAHA.vue";
|
import WAHA from "./WAHA.vue";
|
||||||
|
@ -140,6 +142,7 @@ const NotificationFormList = {
|
||||||
"threema": Threema,
|
"threema": Threema,
|
||||||
"twilio": Twilio,
|
"twilio": Twilio,
|
||||||
"Splunk": Splunk,
|
"Splunk": Splunk,
|
||||||
|
"SpugPush": SpugPush,
|
||||||
"webhook": Webhook,
|
"webhook": Webhook,
|
||||||
"WeCom": WeCom,
|
"WeCom": WeCom,
|
||||||
"GoAlert": GoAlert,
|
"GoAlert": GoAlert,
|
||||||
|
@ -147,6 +150,7 @@ const NotificationFormList = {
|
||||||
"ZohoCliq": ZohoCliq,
|
"ZohoCliq": ZohoCliq,
|
||||||
"SevenIO": SevenIO,
|
"SevenIO": SevenIO,
|
||||||
"whapi": Whapi,
|
"whapi": Whapi,
|
||||||
|
"notifery": Notifery,
|
||||||
"waha": WAHA,
|
"waha": WAHA,
|
||||||
"gtxmessaging": GtxMessaging,
|
"gtxmessaging": GtxMessaging,
|
||||||
"Cellsynt": Cellsynt,
|
"Cellsynt": Cellsynt,
|
||||||
|
|
|
@ -64,6 +64,7 @@
|
||||||
"Expected Value": "Expected Value",
|
"Expected Value": "Expected Value",
|
||||||
"Json Query Expression": "Json Query Expression",
|
"Json Query Expression": "Json Query Expression",
|
||||||
"Friendly Name": "Friendly Name",
|
"Friendly Name": "Friendly Name",
|
||||||
|
"defaultFriendlyName": "New Monitor",
|
||||||
"URL": "URL",
|
"URL": "URL",
|
||||||
"Hostname": "Hostname",
|
"Hostname": "Hostname",
|
||||||
"Host URL": "Host URL",
|
"Host URL": "Host URL",
|
||||||
|
@ -517,6 +518,7 @@
|
||||||
"Clone": "Clone",
|
"Clone": "Clone",
|
||||||
"cloneOf": "Clone of {0}",
|
"cloneOf": "Clone of {0}",
|
||||||
"smtp": "Email (SMTP)",
|
"smtp": "Email (SMTP)",
|
||||||
|
"Use HTML for custom E-mail body": "Use HTML for custom E-mail body",
|
||||||
"secureOptionNone": "None / STARTTLS (25, 587)",
|
"secureOptionNone": "None / STARTTLS (25, 587)",
|
||||||
"secureOptionTLS": "TLS (465)",
|
"secureOptionTLS": "TLS (465)",
|
||||||
"Ignore TLS Error": "Ignore TLS Error",
|
"Ignore TLS Error": "Ignore TLS Error",
|
||||||
|
@ -798,6 +800,7 @@
|
||||||
"PushDeer Server": "PushDeer Server",
|
"PushDeer Server": "PushDeer Server",
|
||||||
"pushDeerServerDescription": "Leave blank to use the official server",
|
"pushDeerServerDescription": "Leave blank to use the official server",
|
||||||
"PushDeer Key": "PushDeer Key",
|
"PushDeer Key": "PushDeer Key",
|
||||||
|
"SpugPush Template Code": "Template Code",
|
||||||
"wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
|
"wayToGetClickSendSMSToken": "You can get API Username and API Key from {0} .",
|
||||||
"Custom Monitor Type": "Custom Monitor Type",
|
"Custom Monitor Type": "Custom Monitor Type",
|
||||||
"Google Analytics ID": "Google Analytics ID",
|
"Google Analytics ID": "Google Analytics ID",
|
||||||
|
@ -1080,6 +1083,8 @@
|
||||||
"pingPerRequestTimeoutLabel": "Per-Ping Timeout",
|
"pingPerRequestTimeoutLabel": "Per-Ping Timeout",
|
||||||
"pingPerRequestTimeoutDescription": "This is the maximum waiting time (in seconds) before considering a single ping packet lost",
|
"pingPerRequestTimeoutDescription": "This is the maximum waiting time (in seconds) before considering a single ping packet lost",
|
||||||
"pingIntervalAdjustedInfo": "Interval adjusted based on packet count, global timeout and per-ping timeout",
|
"pingIntervalAdjustedInfo": "Interval adjusted based on packet count, global timeout and per-ping timeout",
|
||||||
|
"Custom URL": "Custom URL",
|
||||||
|
"customUrlDescription": "Will be used as the clickable URL instead of the monitor's one.",
|
||||||
"OneChatAccessToken": "OneChat Access Token",
|
"OneChatAccessToken": "OneChat Access Token",
|
||||||
"OneChatUserIdOrGroupId": "OneChat User ID or Group ID",
|
"OneChatUserIdOrGroupId": "OneChat User ID or Group ID",
|
||||||
"OneChatBotId": "OneChat Bot ID",
|
"OneChatBotId": "OneChat Bot ID",
|
||||||
|
@ -1100,5 +1105,6 @@
|
||||||
"the smsplanet documentation": "the smsplanet documentation",
|
"the smsplanet documentation": "the smsplanet documentation",
|
||||||
"Phone numbers": "Phone numbers",
|
"Phone numbers": "Phone numbers",
|
||||||
"Sender name": "Sender name",
|
"Sender name": "Sender name",
|
||||||
"smsplanetNeedToApproveName": "Needs to be approved in the client panel"
|
"smsplanetNeedToApproveName": "Needs to be approved in the client panel",
|
||||||
|
"Disable URL in Notification": "Disable URL in Notification"
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,7 +109,7 @@
|
||||||
<!-- Friendly Name -->
|
<!-- Friendly Name -->
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
||||||
<input id="name" v-model="monitor.name" type="text" class="form-control" required data-testid="friendly-name-input">
|
<input id="name" v-model="monitor.name" type="text" class="form-control" data-testid="friendly-name-input" :placeholder="defaultFriendlyName">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- URL -->
|
<!-- URL -->
|
||||||
|
@ -1218,6 +1218,25 @@ export default {
|
||||||
return "";
|
return "";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
defaultFriendlyName() {
|
||||||
|
if (this.monitor.hostname) {
|
||||||
|
return this.monitor.hostname;
|
||||||
|
}
|
||||||
|
if (this.monitor.url) {
|
||||||
|
if (this.monitor.url !== "http://" && this.monitor.url !== "https://") {
|
||||||
|
// Ensure monitor without a URL is not affected by invisible URL.
|
||||||
|
try {
|
||||||
|
const url = new URL(this.monitor.url);
|
||||||
|
return url.hostname;
|
||||||
|
} catch (e) {
|
||||||
|
return this.monitor.url.replace(/https?:\/\//, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default placeholder if neither hostname nor URL is available
|
||||||
|
return this.$t("defaultFriendlyName");
|
||||||
|
},
|
||||||
|
|
||||||
ipRegex() {
|
ipRegex() {
|
||||||
|
|
||||||
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
// Allow to test with simple dns server with port (127.0.0.1:5300)
|
||||||
|
@ -1236,6 +1255,7 @@ export default {
|
||||||
}
|
}
|
||||||
return this.$t(name);
|
return this.$t(name);
|
||||||
},
|
},
|
||||||
|
|
||||||
remoteBrowsersOptions() {
|
remoteBrowsersOptions() {
|
||||||
return this.$root.remoteBrowserList.map(browser => {
|
return this.$root.remoteBrowserList.map(browser => {
|
||||||
return {
|
return {
|
||||||
|
@ -1244,6 +1264,7 @@ export default {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
remoteBrowsersToggle: {
|
remoteBrowsersToggle: {
|
||||||
get() {
|
get() {
|
||||||
return this.remoteBrowsersEnabled || this.monitor.remote_browser != null;
|
return this.remoteBrowsersEnabled || this.monitor.remote_browser != null;
|
||||||
|
@ -1261,6 +1282,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
isAdd() {
|
isAdd() {
|
||||||
return this.$route.path === "/add";
|
return this.$route.path === "/add";
|
||||||
},
|
},
|
||||||
|
@ -1311,6 +1333,7 @@ message HealthCheckResponse {
|
||||||
}
|
}
|
||||||
` ]);
|
` ]);
|
||||||
},
|
},
|
||||||
|
|
||||||
bodyPlaceholder() {
|
bodyPlaceholder() {
|
||||||
if (this.monitor && this.monitor.httpBodyEncoding && this.monitor.httpBodyEncoding === "xml") {
|
if (this.monitor && this.monitor.httpBodyEncoding && this.monitor.httpBodyEncoding === "xml") {
|
||||||
return this.$t("Example:", [ `
|
return this.$t("Example:", [ `
|
||||||
|
@ -1788,6 +1811,10 @@ message HealthCheckResponse {
|
||||||
|
|
||||||
this.processing = true;
|
this.processing = true;
|
||||||
|
|
||||||
|
if (!this.monitor.name) {
|
||||||
|
this.monitor.name = this.defaultFriendlyName;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isInputValid()) {
|
if (!this.isInputValid()) {
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
return;
|
return;
|
||||||
|
|
83
test/e2e/specs/fridendly-name.spec.js
Normal file
83
test/e2e/specs/fridendly-name.spec.js
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import { expect, test } from "@playwright/test";
|
||||||
|
import { login, restoreSqliteSnapshot, screenshot } from "../util-test";
|
||||||
|
|
||||||
|
test.describe("Friendly Name Tests", () => {
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await restoreSqliteSnapshot(page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("hostname", async ({ page }, testInfo) => {
|
||||||
|
// Test DNS monitor with hostname
|
||||||
|
await page.goto("./add");
|
||||||
|
await login(page);
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("monitor-type-select").selectOption("dns");
|
||||||
|
await page.getByTestId("hostname-input").fill("example.com");
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("save-button").click();
|
||||||
|
await page.waitForURL("/dashboard/*");
|
||||||
|
|
||||||
|
expect(page.getByTestId("monitor-list")).toContainText("example.com");
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("URL hostname", async ({ page }, testInfo) => {
|
||||||
|
// Test HTTP monitor with URL
|
||||||
|
await page.goto("./add");
|
||||||
|
await login(page);
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("monitor-type-select").selectOption("http");
|
||||||
|
await page.getByTestId("url-input").fill("https://www.example.com/");
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("save-button").click();
|
||||||
|
await page.waitForURL("/dashboard/*");
|
||||||
|
|
||||||
|
expect(page.getByTestId("monitor-list")).toContainText("www.example.com");
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("custom friendly name", async ({ page }, testInfo) => {
|
||||||
|
// Test custom friendly name for HTTP monitor
|
||||||
|
await page.goto("./add");
|
||||||
|
await login(page);
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("monitor-type-select").selectOption("http");
|
||||||
|
await page.getByTestId("url-input").fill("https://www.example.com/");
|
||||||
|
|
||||||
|
// Check if the friendly name placeholder is set to the hostname
|
||||||
|
const friendlyNameInput = page.getByTestId("friendly-name-input");
|
||||||
|
expect(friendlyNameInput).toHaveAttribute("placeholder", "www.example.com");
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
const customName = "Example Monitor";
|
||||||
|
await friendlyNameInput.fill(customName);
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("save-button").click();
|
||||||
|
await page.waitForURL("/dashboard/*");
|
||||||
|
|
||||||
|
expect(page.getByTestId("monitor-list")).toContainText(customName);
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("default friendly name", async ({ page }, testInfo) => {
|
||||||
|
// Test default friendly name when no custom name is provided
|
||||||
|
await page.goto("./add");
|
||||||
|
await login(page);
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("monitor-type-select").selectOption("group");
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
|
||||||
|
await page.getByTestId("save-button").click();
|
||||||
|
await page.waitForURL("/dashboard/*");
|
||||||
|
|
||||||
|
expect(page.getByTestId("monitor-list")).toContainText("New Monitor");
|
||||||
|
await screenshot(testInfo, page);
|
||||||
|
});
|
||||||
|
});
|
|
@ -12,6 +12,8 @@ test.describe("Status Page", () => {
|
||||||
const monitorName = "Monitor for Status Page";
|
const monitorName = "Monitor for Status Page";
|
||||||
const tagName = "Client";
|
const tagName = "Client";
|
||||||
const tagValue = "Acme Inc";
|
const tagValue = "Acme Inc";
|
||||||
|
const monitorUrl = "https://www.example.com/status";
|
||||||
|
const monitorCustomUrl = "https://www.example.com";
|
||||||
|
|
||||||
// Status Page
|
// Status Page
|
||||||
const footerText = "This is footer text.";
|
const footerText = "This is footer text.";
|
||||||
|
@ -30,7 +32,7 @@ test.describe("Status Page", () => {
|
||||||
await expect(page.getByTestId("monitor-type-select")).toBeVisible();
|
await expect(page.getByTestId("monitor-type-select")).toBeVisible();
|
||||||
await page.getByTestId("monitor-type-select").selectOption("http");
|
await page.getByTestId("monitor-type-select").selectOption("http");
|
||||||
await page.getByTestId("friendly-name-input").fill(monitorName);
|
await page.getByTestId("friendly-name-input").fill(monitorName);
|
||||||
await page.getByTestId("url-input").fill("https://www.example.com/");
|
await page.getByTestId("url-input").fill(monitorUrl);
|
||||||
await page.getByTestId("add-tag-button").click();
|
await page.getByTestId("add-tag-button").click();
|
||||||
await page.getByTestId("tag-name-input").fill(tagName);
|
await page.getByTestId("tag-name-input").fill(tagName);
|
||||||
await page.getByTestId("tag-value-input").fill(tagValue);
|
await page.getByTestId("tag-value-input").fill(tagValue);
|
||||||
|
@ -79,6 +81,13 @@ test.describe("Status Page", () => {
|
||||||
await page.getByTestId("monitor-select").getByRole("option", { name: monitorName }).click();
|
await page.getByTestId("monitor-select").getByRole("option", { name: monitorName }).click();
|
||||||
await expect(page.getByTestId("monitor")).toHaveCount(1);
|
await expect(page.getByTestId("monitor")).toHaveCount(1);
|
||||||
await expect(page.getByTestId("monitor-name")).toContainText(monitorName);
|
await expect(page.getByTestId("monitor-name")).toContainText(monitorName);
|
||||||
|
await expect(page.getByTestId("monitor-name")).not.toHaveAttribute("href");
|
||||||
|
|
||||||
|
// Set public url on
|
||||||
|
await page.getByTestId("monitor-settings").click();
|
||||||
|
await page.getByTestId("show-clickable-link").check();
|
||||||
|
await page.getByTestId("custom-url-input").fill(monitorCustomUrl);
|
||||||
|
await page.getByTestId("monitor-settings-close").click();
|
||||||
|
|
||||||
// Save the changes
|
// Save the changes
|
||||||
await screenshot(testInfo, page);
|
await screenshot(testInfo, page);
|
||||||
|
@ -94,6 +103,8 @@ test.describe("Status Page", () => {
|
||||||
await expect(page.getByTestId("footer-text")).toContainText(footerText);
|
await expect(page.getByTestId("footer-text")).toContainText(footerText);
|
||||||
await expect(page.getByTestId("powered-by")).toHaveCount(0);
|
await expect(page.getByTestId("powered-by")).toHaveCount(0);
|
||||||
|
|
||||||
|
await expect(page.getByTestId("monitor-name")).toHaveAttribute("href", monitorCustomUrl);
|
||||||
|
|
||||||
await expect(page.getByTestId("update-countdown-text")).toContainText("00:");
|
await expect(page.getByTestId("update-countdown-text")).toContainText("00:");
|
||||||
const updateCountdown = Number((await page.getByTestId("update-countdown-text").textContent()).match(/(\d+):(\d+)/)[2]);
|
const updateCountdown = Number((await page.getByTestId("update-countdown-text").textContent()).match(/(\d+):(\d+)/)[2]);
|
||||||
expect(updateCountdown).toBeGreaterThanOrEqual(refreshInterval); // cant be certain when the timer will start, so ensure it's within expected range
|
expect(updateCountdown).toBeGreaterThanOrEqual(refreshInterval); // cant be certain when the timer will start, so ensure it's within expected range
|
||||||
|
|
Loading…
Add table
Reference in a new issue