mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-07 13:32:35 +02:00
Merge remote-tracking branch 'origin/master' into feature/monitor-checks
This commit is contained in:
commit
70ac68368d
41 changed files with 1482 additions and 2302 deletions
|
@ -1,6 +1,7 @@
|
|||
# Uptime Kuma
|
||||
|
||||
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a>
|
||||
<a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/stars/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/pulls/louislam/uptime-kuma" /></a> <a target="_blank" href="https://hub.docker.com/r/louislam/uptime-kuma"><img src="https://img.shields.io/docker/v/louislam/uptime-kuma/latest?label=docker%20image%20ver." /></a> <a target="_blank" href="https://github.com/louislam/uptime-kuma"><img src="https://img.shields.io/github/last-commit/louislam/uptime-kuma" /></a> <a target="_blank" href="https://opencollective.com/uptime-kuma"><img src="https://opencollective.com/uptime-kuma/total/badge.svg?label=Backers&color=brightgreen" /></a>
|
||||
|
||||
|
||||
<div align="center" width="100%">
|
||||
<img src="./public/icon.svg" width="128" alt="" />
|
||||
|
|
7
db/patch-monitor-push_token.sql
Normal file
7
db/patch-monitor-push_token.sql
Normal file
|
@ -0,0 +1,7 @@
|
|||
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE monitor
|
||||
ADD push_token VARCHAR(20) DEFAULT NULL;
|
||||
|
||||
COMMIT;
|
24
dockerfile
24
dockerfile
|
@ -31,3 +31,27 @@ CMD ["node", "server/server.js"]
|
|||
|
||||
FROM release AS nightly
|
||||
RUN npm run mark-as-nightly
|
||||
|
||||
# Upload the artifact to Github
|
||||
FROM node:14-buster-slim AS upload-artifact
|
||||
WORKDIR /
|
||||
RUN apt update && \
|
||||
apt --yes install curl file
|
||||
|
||||
ARG GITHUB_TOKEN
|
||||
ARG TARGETARCH
|
||||
ARG PLATFORM=debian
|
||||
ARG VERSION=1.5.0
|
||||
|
||||
|
||||
COPY --from=build /app /app
|
||||
|
||||
RUN FILE=uptime-kuma.tar.gz
|
||||
RUN tar -czf $FILE app
|
||||
|
||||
RUN curl \
|
||||
-H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Content-Type: $(file -b --mime-type $FILE)" \
|
||||
--data-binary @$FILE \
|
||||
"https://uploads.github.com/repos/louislam/uptime-kuma/releases/$VERSION/assets?name=$(basename $FILE)"
|
||||
|
||||
|
|
2368
package-lock.json
generated
2368
package-lock.json
generated
File diff suppressed because it is too large
Load diff
11
package.json
11
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "uptime-kuma",
|
||||
"version": "1.7.1",
|
||||
"version": "1.7.3",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -23,12 +23,13 @@
|
|||
"tsc": "tsc",
|
||||
"vite-preview-dist": "vite preview --host",
|
||||
"build-docker": "npm run build-docker-debian && npm run build-docker-alpine",
|
||||
"build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.7.1-alpine --target release . --push",
|
||||
"build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.7.1 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.7.1-debian --target release . --push",
|
||||
"build-docker-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:alpine -t louislam/uptime-kuma:1-alpine -t louislam/uptime-kuma:1.7.3-alpine --target release . --push",
|
||||
"build-docker-debian": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma -t louislam/uptime-kuma:1 -t louislam/uptime-kuma:1.7.3 -t louislam/uptime-kuma:debian -t louislam/uptime-kuma:1-debian -t louislam/uptime-kuma:1.7.3-debian --target release . --push",
|
||||
"build-docker-nightly": "docker buildx build --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly --target nightly . --push",
|
||||
"build-docker-nightly-alpine": "docker buildx build -f dockerfile-alpine --platform linux/amd64,linux/arm64,linux/arm/v7 -t louislam/uptime-kuma:nightly-alpine --target nightly . --push",
|
||||
"build-docker-nightly-amd64": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:nightly-amd64 --target nightly . --push --progress plain",
|
||||
"setup": "git checkout 1.7.1 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune",
|
||||
"upload-artifacts": "docker buildx build --platform linux/amd64 -t louislam/uptime-kuma:upload-artifact --build-arg GITHUB_TOKEN --target upload-artifact . --progress plain",
|
||||
"setup": "git checkout 1.7.3 && npm install --legacy-peer-deps && node node_modules/esbuild/install.js && npm run build && npm prune",
|
||||
"update-version": "node extra/update-version.js",
|
||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||
"reset-password": "node extra/reset-password.js",
|
||||
|
@ -104,6 +105,6 @@
|
|||
"stylelint": "^13.13.1",
|
||||
"stylelint-config-standard": "^22.0.0",
|
||||
"typescript": "^4.4.3",
|
||||
"vite": "^2.5.10"
|
||||
"vite": "2.5.*"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ class Database {
|
|||
"patch-add-retry-interval-monitor.sql": true,
|
||||
"patch-incident-table.sql": true,
|
||||
"patch-group-table.sql": true,
|
||||
"patch-monitor-push_token.sql": true,
|
||||
"patch-add-monitor-checks-table.sql": true,
|
||||
}
|
||||
|
||||
|
|
|
@ -69,6 +69,7 @@ class Monitor extends BeanModel {
|
|||
dns_resolve_type: this.dns_resolve_type,
|
||||
dns_resolve_server: this.dns_resolve_server,
|
||||
dns_last_result: this.dns_last_result,
|
||||
pushToken: this.pushToken,
|
||||
notificationIDList,
|
||||
checks: checks,
|
||||
tags: tags,
|
||||
|
@ -232,6 +233,28 @@ class Monitor extends BeanModel {
|
|||
|
||||
bean.msg = dnsMessage;
|
||||
bean.status = UP;
|
||||
} else if (this.type === "push") { // Type: Push
|
||||
const time = R.isoDateTime(dayjs.utc().subtract(this.interval, "second"));
|
||||
|
||||
let heartbeatCount = await R.count("heartbeat", " monitor_id = ? AND time > ? ", [
|
||||
this.id,
|
||||
time
|
||||
]);
|
||||
|
||||
debug("heartbeatCount" + heartbeatCount + " " + time);
|
||||
|
||||
if (heartbeatCount <= 0) {
|
||||
throw new Error("No heartbeat in the time window");
|
||||
} else {
|
||||
// No need to insert successful heartbeat for push type, so end here
|
||||
retries = 0;
|
||||
this.heartbeatInterval = setTimeout(beat, this.interval * 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
} else {
|
||||
bean.msg = "Unknown Monitor Type";
|
||||
bean.status = PENDING;
|
||||
}
|
||||
|
||||
if (this.isUpsideDown()) {
|
||||
|
@ -259,6 +282,8 @@ class Monitor extends BeanModel {
|
|||
}
|
||||
}
|
||||
|
||||
let beatInterval = this.interval;
|
||||
|
||||
// * ? -> ANY STATUS = important [isFirstBeat]
|
||||
// UP -> PENDING = not important
|
||||
// * UP -> DOWN = important
|
||||
|
@ -308,8 +333,6 @@ class Monitor extends BeanModel {
|
|||
bean.important = false;
|
||||
}
|
||||
|
||||
let beatInterval = this.interval;
|
||||
|
||||
if (bean.status === UP) {
|
||||
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
||||
} else if (bean.status === PENDING) {
|
||||
|
@ -335,7 +358,14 @@ class Monitor extends BeanModel {
|
|||
|
||||
};
|
||||
|
||||
beat();
|
||||
// Delay Push Type
|
||||
if (this.type === "push") {
|
||||
setTimeout(() => {
|
||||
beat();
|
||||
}, this.interval * 1000);
|
||||
} else {
|
||||
beat();
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
|
|
|
@ -4,15 +4,56 @@ const { R } = require("redbean-node");
|
|||
const server = require("../server");
|
||||
const apicache = require("../modules/apicache");
|
||||
const Monitor = require("../model/monitor");
|
||||
const dayjs = require("dayjs");
|
||||
const { UP } = require("../../src/util");
|
||||
let router = express.Router();
|
||||
|
||||
let cache = apicache.middleware;
|
||||
let io = server.io;
|
||||
|
||||
router.get("/api/entry-page", async (_, response) => {
|
||||
allowDevAllOrigin(response);
|
||||
response.json(server.entryPage);
|
||||
});
|
||||
|
||||
router.get("/api/push/:pushToken", async (request, response) => {
|
||||
try {
|
||||
let pushToken = request.params.pushToken;
|
||||
let msg = request.query.msg || "OK";
|
||||
|
||||
let monitor = await R.findOne("monitor", " push_token = ? AND active = 1 ", [
|
||||
pushToken
|
||||
]);
|
||||
|
||||
if (! monitor) {
|
||||
throw new Error("Monitor not found or not active.");
|
||||
}
|
||||
|
||||
let bean = R.dispense("heartbeat");
|
||||
bean.monitor_id = monitor.id;
|
||||
bean.time = R.isoDateTime(dayjs.utc());
|
||||
bean.status = UP;
|
||||
bean.msg = msg;
|
||||
|
||||
// TODO: HOW TO?
|
||||
//bean.ping
|
||||
|
||||
await R.store(bean);
|
||||
|
||||
io.to(monitor.user_id).emit("heartbeat", bean.toJSON());
|
||||
Monitor.sendStats(io, monitor.id, monitor.user_id);
|
||||
|
||||
response.json({
|
||||
ok: true,
|
||||
});
|
||||
} catch (e) {
|
||||
response.json({
|
||||
ok: false,
|
||||
msg: e.message
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Status Page Config
|
||||
router.get("/api/status-page/config", async (_request, response) => {
|
||||
allowDevAllOrigin(response);
|
||||
|
|
|
@ -6,11 +6,7 @@ if (!process.env.NODE_ENV) {
|
|||
|
||||
console.log("Node Env: " + process.env.NODE_ENV);
|
||||
|
||||
const {
|
||||
sleep,
|
||||
debug,
|
||||
getRandomInt,
|
||||
} = require("../src/util");
|
||||
const { sleep, debug, getRandomInt, genSecret } = require("../src/util");
|
||||
|
||||
console.log("Importing Node libraries");
|
||||
const fs = require("fs");
|
||||
|
@ -46,8 +42,6 @@ const {
|
|||
setSettings,
|
||||
setting,
|
||||
initJWTSecret,
|
||||
genSecret,
|
||||
allowDevAllOrigin,
|
||||
checkLogin,
|
||||
updateMonitorChecks,
|
||||
} = require("./util-server");
|
||||
|
@ -320,6 +314,12 @@ exports.entryPage = "dashboard";
|
|||
if (user.twofa_status == 0) {
|
||||
let newSecret = await genSecret();
|
||||
let encodedSecret = base32.encode(newSecret);
|
||||
|
||||
// Google authenticator doesn't like equal signs
|
||||
// The fix is found at https://github.com/guyht/notp
|
||||
// Related issue: https://github.com/louislam/uptime-kuma/issues/486
|
||||
encodedSecret = encodedSecret.toString().replace(/=/g, "");
|
||||
|
||||
let uri = `otpauth://totp/Uptime%20Kuma:${user.username}?secret=${encodedSecret}`;
|
||||
|
||||
await R.exec("UPDATE `user` SET twofa_secret = ? WHERE id = ? ", [
|
||||
|
@ -529,6 +529,7 @@ exports.entryPage = "dashboard";
|
|||
bean.maxredirects = monitor.maxredirects;
|
||||
bean.dns_resolve_type = monitor.dns_resolve_type;
|
||||
bean.dns_resolve_server = monitor.dns_resolve_server;
|
||||
bean.pushToken = monitor.pushToken;
|
||||
|
||||
const checks = monitor.checks;
|
||||
delete monitor.checks;
|
||||
|
|
|
@ -272,16 +272,6 @@ exports.getTotalClientInRoom = (io, roomName) => {
|
|||
}
|
||||
};
|
||||
|
||||
exports.genSecret = () => {
|
||||
let secret = "";
|
||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let charsLength = chars.length;
|
||||
for ( let i = 0; i < 64; i++ ) {
|
||||
secret += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return secret;
|
||||
};
|
||||
|
||||
exports.allowDevAllOrigin = (res) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
exports.allowAllOrigin(res);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
@import "vars.scss";
|
||||
@import "multiselect.scss";
|
||||
@import "node_modules/bootstrap/scss/bootstrap";
|
||||
|
||||
#app {
|
||||
|
@ -179,6 +180,11 @@ h2 {
|
|||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.form-control:disabled, .form-control[readonly] {
|
||||
background-color: #232f3b;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.table-hover > tbody > tr:hover {
|
||||
--bs-table-accent-bg: #070a10;
|
||||
color: $dark-font-color;
|
||||
|
@ -233,30 +239,6 @@ h2 {
|
|||
color: $dark-font-color;
|
||||
}
|
||||
|
||||
// Multiselect
|
||||
.multiselect__tags {
|
||||
background-color: $dark-bg2;
|
||||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.multiselect__input, .multiselect__single {
|
||||
background-color: $dark-bg2;
|
||||
color: $dark-font-color;
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
background-color: $dark-bg2;
|
||||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.multiselect__option--selected {
|
||||
background-color: $dark-bg;
|
||||
}
|
||||
|
||||
.monitor-list {
|
||||
.item {
|
||||
&:hover {
|
||||
|
|
73
src/assets/multiselect.scss
Normal file
73
src/assets/multiselect.scss
Normal file
|
@ -0,0 +1,73 @@
|
|||
@import "vars.scss";
|
||||
@import "node_modules/vue-multiselect/dist/vue-multiselect";
|
||||
|
||||
.multiselect__tags {
|
||||
border-radius: 1.5rem;
|
||||
border: 1px solid #ced4da;
|
||||
min-height: 38px;
|
||||
padding: 6px 40px 0 8px;
|
||||
}
|
||||
|
||||
.multiselect--active .multiselect__tags {
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight {
|
||||
background: $primary !important;
|
||||
}
|
||||
|
||||
.multiselect__option--highlight::after {
|
||||
background: $primary !important;
|
||||
}
|
||||
|
||||
.multiselect__tag {
|
||||
border-radius: 50rem;
|
||||
margin-bottom: 0;
|
||||
padding: 6px 26px 6px 10px;
|
||||
background: $primary !important;
|
||||
}
|
||||
|
||||
.multiselect__placeholder {
|
||||
font-size: 1rem;
|
||||
padding-left: 6px;
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
margin-bottom: 0;
|
||||
opacity: 0.67;
|
||||
}
|
||||
|
||||
.multiselect__input,
|
||||
.multiselect__single {
|
||||
line-height: 14px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.dark {
|
||||
.multiselect__tag {
|
||||
color: $dark-font-color2;
|
||||
}
|
||||
|
||||
.multiselect__tags {
|
||||
background-color: $dark-bg2;
|
||||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.multiselect__input,
|
||||
.multiselect__single {
|
||||
background-color: $dark-bg2;
|
||||
color: $dark-font-color;
|
||||
}
|
||||
|
||||
.multiselect__content-wrapper {
|
||||
background-color: $dark-bg2;
|
||||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.multiselect__option--selected {
|
||||
background-color: $dark-bg;
|
||||
}
|
||||
}
|
122
src/components/CopyableInput.vue
Normal file
122
src/components/CopyableInput.vue
Normal file
|
@ -0,0 +1,122 @@
|
|||
<template>
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
:id="id"
|
||||
ref="input"
|
||||
v-model="model"
|
||||
:type="type"
|
||||
class="form-control"
|
||||
:placeholder="placeholder"
|
||||
:autocomplete="autocomplete"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
>
|
||||
|
||||
<a class="btn btn-outline-primary" @click="copyToClipboard(model)">
|
||||
<font-awesome-icon :icon="icon" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
let timeout;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "text"
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
required: {
|
||||
type: Boolean
|
||||
},
|
||||
readonly: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
disabled: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visibility: "password",
|
||||
icon: "copy",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
showInput() {
|
||||
this.visibility = "text";
|
||||
},
|
||||
|
||||
hideInput() {
|
||||
this.visibility = "password";
|
||||
},
|
||||
|
||||
copyToClipboard(textToCopy) {
|
||||
this.icon = "check";
|
||||
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
this.icon = "copy";
|
||||
}, 3000);
|
||||
|
||||
// navigator clipboard api needs a secure context (https)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
// navigator clipboard api method'
|
||||
return navigator.clipboard.writeText(textToCopy);
|
||||
} else {
|
||||
// text area method
|
||||
let textArea = document.createElement("textarea");
|
||||
textArea.value = textToCopy;
|
||||
// make the textarea out of viewport
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.left = "-999999px";
|
||||
textArea.style.top = "-999999px";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
return new Promise((res, rej) => {
|
||||
// here the magic happens
|
||||
document.execCommand("copy") ? res() : rej();
|
||||
textArea.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
|
@ -13,23 +13,7 @@
|
|||
<div class="mb-3">
|
||||
<label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label>
|
||||
<select id="notification-type" v-model="notification.type" class="form-select">
|
||||
<option value="telegram">Telegram</option>
|
||||
<option value="webhook">Webhook</option>
|
||||
<option value="smtp">{{ $t("Email") }} (SMTP)</option>
|
||||
<option value="discord">Discord</option>
|
||||
<option value="teams">Microsoft Teams</option>
|
||||
<option value="signal">Signal</option>
|
||||
<option value="gotify">Gotify</option>
|
||||
<option value="slack">Slack</option>
|
||||
<option value="rocket.chat">Rocket.chat</option>
|
||||
<option value="pushover">Pushover</option>
|
||||
<option value="pushy">Pushy</option>
|
||||
<option value="octopush">Octopush</option>
|
||||
<option value="lunasea">LunaSea</option>
|
||||
<option value="apprise">Apprise (Support 50+ Notification services)</option>
|
||||
<option value="pushbullet">Pushbullet</option>
|
||||
<option value="line">Line Messenger</option>
|
||||
<option value="mattermost">Mattermost</option>
|
||||
<option v-for="type in notificationTypes" :key="type" :value="type">{{ $t(type) }}</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
@ -38,370 +22,8 @@
|
|||
<input id="notification-name" v-model="notification.name" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<Telegram v-if="notification.type === 'telegram'" />
|
||||
|
||||
<!-- TODO: Convert all into vue components, but not an easy task. -->
|
||||
|
||||
<template v-if="notification.type === 'webhook'">
|
||||
<div class="mb-3">
|
||||
<label for="webhook-url" class="form-label">Post URL</label>
|
||||
<input id="webhook-url" v-model="notification.webhookURL" type="url" pattern="https?://.+" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="webhook-content-type" class="form-label">Content Type</label>
|
||||
<select id="webhook-content-type" v-model="notification.webhookContentType" class="form-select" required>
|
||||
<option value="json">
|
||||
application/json
|
||||
</option>
|
||||
<option value="form-data">
|
||||
multipart/form-data
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div class="form-text">
|
||||
<p>"application/json" is good for any modern http servers such as express.js</p>
|
||||
<p>"multipart/form-data" is good for PHP, you just need to parse the json by <strong>json_decode($_POST['data'])</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<SMTP v-if="notification.type === 'smtp'" />
|
||||
|
||||
<template v-if="notification.type === 'discord'">
|
||||
<div class="mb-3">
|
||||
<label for="discord-webhook-url" class="form-label">Discord Webhook URL</label>
|
||||
<input id="discord-webhook-url" v-model="notification.discordWebhookUrl" type="text" class="form-control" required autocomplete="false">
|
||||
<div class="form-text">
|
||||
You can get this by going to Server Settings -> Integrations -> Create Webhook
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="discord-username" class="form-label">Bot Display Name</label>
|
||||
<input id="discord-username" v-model="notification.discordUsername" type="text" class="form-control" autocomplete="false" :placeholder="$root.appName">
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="discord-prefix-message" class="form-label">Prefix Custom Message</label>
|
||||
<input id="discord-prefix-message" v-model="notification.discordPrefixMessage" type="text" class="form-control" autocomplete="false" placeholder="Hello @everyone is...">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'signal'">
|
||||
<div class="mb-3">
|
||||
<label for="signal-url" class="form-label">Post URL</label>
|
||||
<input id="signal-url" v-model="notification.signalURL" type="url" pattern="https?://.+" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="signal-number" class="form-label">Number</label>
|
||||
<input id="signal-number" v-model="notification.signalNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="signal-recipients" class="form-label">Recipients</label>
|
||||
<input id="signal-recipients" v-model="notification.signalRecipients" type="text" class="form-control" required>
|
||||
|
||||
<div class="form-text">
|
||||
You need to have a signal client with REST API.
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
You can check this url to view how to setup one:
|
||||
</p>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
<a href="https://github.com/bbernhard/signal-cli-rest-api" target="_blank">https://github.com/bbernhard/signal-cli-rest-api</a>
|
||||
</p>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
IMPORTANT: You cannot mix groups and numbers in recipients!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'gotify'">
|
||||
<div class="mb-3">
|
||||
<label for="gotify-application-token" class="form-label">Application Token</label>
|
||||
<HiddenInput id="gotify-application-token" v-model="notification.gotifyapplicationToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="gotify-server-url" class="form-label">Server URL</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="gotify-server-url" v-model="notification.gotifyserverurl" type="text" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="gotify-priority" class="form-label">Priority</label>
|
||||
<input id="gotify-priority" v-model="notification.gotifyPriority" type="number" class="form-control" required min="0" max="10" step="1">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'slack'">
|
||||
<div class="mb-3">
|
||||
<label for="slack-webhook-url" class="form-label">Webhook URL<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="slack-webhook-url" v-model="notification.slackwebhookURL" type="text" class="form-control" required>
|
||||
<label for="slack-username" class="form-label">Username</label>
|
||||
<input id="slack-username" v-model="notification.slackusername" type="text" class="form-control">
|
||||
<label for="slack-iconemo" class="form-label">Icon Emoji</label>
|
||||
<input id="slack-iconemo" v-model="notification.slackiconemo" type="text" class="form-control">
|
||||
<label for="slack-channel" class="form-label">Channel Name</label>
|
||||
<input id="slack-channel-name" v-model="notification.slackchannel" type="text" class="form-control">
|
||||
<label for="slack-button-url" class="form-label">Uptime Kuma URL</label>
|
||||
<input id="slack-button" v-model="notification.slackbutton" 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://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a>
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
Enter the channel name on Slack Channel Name field if you want to bypass the webhook channel. 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;">
|
||||
Emoji cheat sheet: <a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'rocket.chat'">
|
||||
<div class="mb-3">
|
||||
<label for="rocket-webhook-url" class="form-label">Webhook URL<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="rocket-webhook-url" v-model="notification.rocketwebhookURL" type="text" class="form-control" required>
|
||||
<label for="rocket-username" class="form-label">Username</label>
|
||||
<input id="rocket-username" v-model="notification.rocketusername" type="text" class="form-control">
|
||||
<label for="rocket-iconemo" class="form-label">Icon Emoji</label>
|
||||
<input id="rocket-iconemo" v-model="notification.rocketiconemo" type="text" class="form-control">
|
||||
<label for="rocket-channel" class="form-label">Channel Name</label>
|
||||
<input id="rocket-channel-name" v-model="notification.rocketchannel" type="text" class="form-control">
|
||||
<label for="rocket-button-url" class="form-label">Uptime Kuma URL</label>
|
||||
<input id="rocket-button" v-model="notification.rocketbutton" 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.rocket.chat/guides/administration/administration/integrations" target="_blank">https://api.slack.com/messaging/webhooks</a>
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
Enter the channel name on Rocket.chat Channel Name field if you want to bypass the webhook channel. 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;">
|
||||
Emoji cheat sheet: <a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</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'">
|
||||
<div class="mb-3">
|
||||
<label for="pushy-app-token" class="form-label">API_KEY</label>
|
||||
<HiddenInput id="pushy-app-token" v-model="notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pushy-user-key" class="form-label">USER_TOKEN</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="pushy-user-key" v-model="notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://pushy.me/docs/api/send-notifications" target="_blank">https://pushy.me/docs/api/send-notifications</a>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'octopush'">
|
||||
<div class="mb-3">
|
||||
<label for="octopush-key" class="form-label">API KEY</label>
|
||||
<HiddenInput id="octopush-key" v-model="notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<label for="octopush-login" class="form-label">API LOGIN</label>
|
||||
<input id="octopush-login" v-model="notification.octopushLogin" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-type-sms" class="form-label">SMS Type</label>
|
||||
<select id="octopush-type-sms" v-model="notification.octopushSMSType" class="form-select">
|
||||
<option value="sms_premium">Premium (Fast - recommended for alerting)</option>
|
||||
<option value="sms_low_cost">Low Cost (Slow, sometimes blocked by operator)</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Check octopush prices <a href="https://octopush.com/tarifs-sms-international/" target="_blank">https://octopush.com/tarifs-sms-international/</a>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-phone-number" class="form-label">Phone number (intl format, eg : +33612345678) </label>
|
||||
<input id="octopush-phone-number" v-model="notification.octopushPhoneNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-sender-name" class="form-label">SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)</label>
|
||||
<input id="octopush-sender-name" v-model="notification.octopushSenderName" type="text" minlength="3" maxlength="11" class="form-control">
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://octopush.com/api-sms-documentation/envoi-de-sms/" target="_blank">https://octopush.com/api-sms-documentation/envoi-de-sms/</a>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'pushover'">
|
||||
<div class="mb-3">
|
||||
<label for="pushover-user" class="form-label">User Key<span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="pushover-user" v-model="notification.pushoveruserkey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<label for="pushover-app-token" class="form-label">Application Token<span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="pushover-app-token" v-model="notification.pushoverapptoken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<label for="pushover-device" class="form-label">Device</label>
|
||||
<input id="pushover-device" v-model="notification.pushoverdevice" type="text" class="form-control">
|
||||
<label for="pushover-device" class="form-label">Message Title</label>
|
||||
<input id="pushover-title" v-model="notification.pushovertitle" type="text" class="form-control">
|
||||
<label for="pushover-priority" class="form-label">Priority</label>
|
||||
<select id="pushover-priority" v-model="notification.pushoverpriority" class="form-select">
|
||||
<option>-2</option>
|
||||
<option>-1</option>
|
||||
<option>0</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
</select>
|
||||
<label for="pushover-sound" class="form-label">Notification Sound</label>
|
||||
<select id="pushover-sound" v-model="notification.pushoversounds" class="form-select">
|
||||
<option>pushover</option>
|
||||
<option>bike</option>
|
||||
<option>bugle</option>
|
||||
<option>cashregister</option>
|
||||
<option>classical</option>
|
||||
<option>cosmic</option>
|
||||
<option>falling</option>
|
||||
<option>gamelan</option>
|
||||
<option>incoming</option>
|
||||
<option>intermission</option>
|
||||
<option>mechanical</option>
|
||||
<option>pianobar</option>
|
||||
<option>siren</option>
|
||||
<option>spacealarm</option>
|
||||
<option>tugboat</option>
|
||||
<option>alien</option>
|
||||
<option>climb</option>
|
||||
<option>persistent</option>
|
||||
<option>echo</option>
|
||||
<option>updown</option>
|
||||
<option>vibrate</option>
|
||||
<option>none</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
<span style="color: red;"><sup>*</sup></span>Required
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://pushover.net/api" target="_blank">https://pushover.net/api</a>
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
If you want to send notifications to different devices, fill out Device field.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'apprise'">
|
||||
<div class="mb-3">
|
||||
<label for="apprise-url" class="form-label">Apprise URL</label>
|
||||
<input id="apprise-url" v-model="notification.appriseURL" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
<p>Example: twilio://AccountSid:AuthToken@FromPhoneNo</p>
|
||||
<p>
|
||||
Read more: <a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<p>
|
||||
Status:
|
||||
<span v-if="appriseInstalled" class="text-primary">Apprise is installed</span>
|
||||
<span v-else class="text-danger">Apprise is not installed. <a href="https://github.com/caronc/apprise" target="_blank">Read more</a></span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'lunasea'">
|
||||
<div class="mb-3">
|
||||
<label for="lunasea-device" class="form-label">LunaSea Device ID<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="lunasea-device" v-model="notification.lunaseaDevice" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
<p><span style="color: red;"><sup>*</sup></span>Required</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'pushbullet'">
|
||||
<div class="mb-3">
|
||||
<label for="pushbullet-access-token" class="form-label">Access Token</label>
|
||||
<HiddenInput id="pushbullet-access-token" v-model="notification.pushbulletAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://docs.pushbullet.com" target="_blank">https://docs.pushbullet.com</a>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<template v-if="notification.type === 'line'">
|
||||
<div class="mb-3">
|
||||
<label for="line-channel-access-token" class="form-label">Channel access token</label>
|
||||
<HiddenInput id="line-channel-access-token" v-model="notification.lineChannelAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
Line Developers Console - <b>Basic Settings</b>
|
||||
</div>
|
||||
<div class="mb-3" style="margin-top: 12px;">
|
||||
<label for="line-user-id" class="form-label">User ID</label>
|
||||
<input id="line-user-id" v-model="notification.lineUserID" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
Line Developers Console - <b>Messaging API</b>
|
||||
</div>
|
||||
<div class="form-text" style="margin-top: 8px;">
|
||||
First access the <a href="https://developers.line.biz/console/" target="_blank">Line Developers Console</a>, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- DEPRECATED! Please create vue component in "./src/components/notifications/{notification name}.vue" -->
|
||||
|
||||
<Teams v-if="notification.type === 'teams'" />
|
||||
<!-- form body -->
|
||||
<component :is="currentForm" />
|
||||
|
||||
<div class="mb-3 mt-4">
|
||||
<hr class="dropdown-divider mb-4">
|
||||
|
@ -450,18 +72,11 @@ import { Modal } from "bootstrap"
|
|||
import { ucfirst } from "../util.ts"
|
||||
|
||||
import Confirm from "./Confirm.vue";
|
||||
import HiddenInput from "./HiddenInput.vue";
|
||||
import Telegram from "./notifications/Telegram.vue";
|
||||
import Teams from "./notifications/Teams.vue";
|
||||
import SMTP from "./notifications/SMTP.vue";
|
||||
import NotificationFormList from "./notifications"
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Confirm,
|
||||
HiddenInput,
|
||||
Telegram,
|
||||
Teams,
|
||||
SMTP,
|
||||
},
|
||||
props: {},
|
||||
emits: ["added"],
|
||||
|
@ -470,13 +85,23 @@ export default {
|
|||
model: null,
|
||||
processing: false,
|
||||
id: null,
|
||||
notificationTypes: Object.keys(NotificationFormList),
|
||||
notification: {
|
||||
name: "",
|
||||
/** @type { null | keyof NotificationFormList } */
|
||||
type: null,
|
||||
isDefault: false,
|
||||
// Do not set default value here, please scroll to show()
|
||||
},
|
||||
appriseInstalled: false,
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
currentForm() {
|
||||
if (!this.notification.type) {
|
||||
return null
|
||||
}
|
||||
return NotificationFormList[this.notification.type]
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -497,10 +122,6 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
this.modal = new Modal(this.$refs.modal)
|
||||
|
||||
this.$root.getSocket().emit("checkApprise", (installed) => {
|
||||
this.appriseInstalled = installed;
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
|
||||
|
@ -528,9 +149,7 @@ export default {
|
|||
}
|
||||
|
||||
// Set Default value here
|
||||
this.notification.type = "telegram";
|
||||
this.notification.gotifyPriority = 8;
|
||||
this.notification.smtpSecure = false;
|
||||
this.notification.type = this.notificationTypes[0];
|
||||
}
|
||||
|
||||
this.modal.show()
|
||||
|
|
34
src/components/notifications/Apprise.vue
Normal file
34
src/components/notifications/Apprise.vue
Normal file
|
@ -0,0 +1,34 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="apprise-url" class="form-label">Apprise URL</label>
|
||||
<input id="apprise-url" v-model="$parent.notification.appriseURL" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
<p>Example: twilio://AccountSid:AuthToken@FromPhoneNo</p>
|
||||
<p>
|
||||
Read more: <a href="https://github.com/caronc/apprise/wiki#notification-services" target="_blank">https://github.com/caronc/apprise/wiki#notification-services</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<p>
|
||||
Status:
|
||||
<span v-if="appriseInstalled" class="text-primary">Apprise is installed</span>
|
||||
<span v-else class="text-danger">Apprise is not installed. <a href="https://github.com/caronc/apprise" target="_blank">Read more</a></span>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
appriseInstalled: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.$root.getSocket().emit("checkApprise", (installed) => {
|
||||
this.appriseInstalled = installed;
|
||||
})
|
||||
},
|
||||
}
|
||||
</script>
|
20
src/components/notifications/Discord.vue
Normal file
20
src/components/notifications/Discord.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="discord-webhook-url" class="form-label">Discord Webhook URL</label>
|
||||
<input id="discord-webhook-url" v-model="$parent.notification.discordWebhookUrl" type="text" class="form-control" required autocomplete="false">
|
||||
<div class="form-text">
|
||||
You can get this by going to Server Settings -> Integrations -> Create Webhook
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="discord-username" class="form-label">Bot Display Name</label>
|
||||
<input id="discord-username" v-model="$parent.notification.discordUsername" type="text" class="form-control" autocomplete="false" :placeholder="$root.appName">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="discord-prefix-message" class="form-label">Prefix Custom Message</label>
|
||||
<input id="discord-prefix-message" v-model="$parent.notification.discordPrefixMessage" type="text" class="form-control" autocomplete="false" placeholder="Hello @everyone is...">
|
||||
</div>
|
||||
</template>
|
32
src/components/notifications/Gotify.vue
Normal file
32
src/components/notifications/Gotify.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="gotify-application-token" class="form-label">Application Token</label>
|
||||
<HiddenInput id="gotify-application-token" v-model="$parent.notification.gotifyapplicationToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="gotify-server-url" class="form-label">Server URL</label>
|
||||
<div class="input-group mb-3">
|
||||
<input id="gotify-server-url" v-model="$parent.notification.gotifyserverurl" type="text" class="form-control" required>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="gotify-priority" class="form-label">Priority</label>
|
||||
<input id="gotify-priority" v-model="$parent.notification.gotifyPriority" type="number" class="form-control" required min="0" max="10" step="1">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
mounted() {
|
||||
if (typeof this.$parent.notification.gotifyPriority === "undefined") {
|
||||
this.$parent.notification.gotifyPriority = 8;
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
29
src/components/notifications/Line.vue
Normal file
29
src/components/notifications/Line.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="line-channel-access-token" class="form-label">Channel access token</label>
|
||||
<HiddenInput id="line-channel-access-token" v-model="$parent.notification.lineChannelAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
Line Developers Console - <b>Basic Settings</b>
|
||||
</div>
|
||||
<div class="mb-3" style="margin-top: 12px;">
|
||||
<label for="line-user-id" class="form-label">User ID</label>
|
||||
<input id="line-user-id" v-model="$parent.notification.lineUserID" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="form-text">
|
||||
Line Developers Console - <b>Messaging API</b>
|
||||
</div>
|
||||
<div class="form-text" style="margin-top: 8px;">
|
||||
First access the <a href="https://developers.line.biz/console/" target="_blank">Line Developers Console</a>, create a provider and channel (Messaging API), then you can get the channel access token and user id from the above mentioned menu items.
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
}
|
||||
</script>
|
9
src/components/notifications/LunaSea.vue
Normal file
9
src/components/notifications/LunaSea.vue
Normal file
|
@ -0,0 +1,9 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="lunasea-device" class="form-label">LunaSea Device ID<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="lunasea-device" v-model="$parent.notification.lunaseaDevice" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
<p><span style="color: red;"><sup>*</sup></span>Required</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
32
src/components/notifications/Mattermost.vue
Normal file
32
src/components/notifications/Mattermost.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<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="$parent.notification.mattermostWebhookUrl" type="text" class="form-control" required>
|
||||
<label for="mattermost-username" class="form-label">Username</label>
|
||||
<input id="mattermost-username" v-model="$parent.notification.mattermostusername" type="text" class="form-control">
|
||||
<label for="mattermost-iconurl" class="form-label">Icon URL</label>
|
||||
<input id="mattermost-iconurl" v-model="$parent.notification.mattermosticonurl" type="text" class="form-control">
|
||||
<label for="mattermost-iconemo" class="form-label">Icon Emoji</label>
|
||||
<input id="mattermost-iconemo" v-model="$parent.notification.mattermosticonemo" type="text" class="form-control">
|
||||
<label for="mattermost-channel" class="form-label">Channel Name</label>
|
||||
<input id="mattermost-channel-name" v-model="$parent.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>
|
40
src/components/notifications/Octopush.vue
Normal file
40
src/components/notifications/Octopush.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-key" class="form-label">API KEY</label>
|
||||
<HiddenInput id="octopush-key" v-model="$parent.notification.octopushAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<label for="octopush-login" class="form-label">API LOGIN</label>
|
||||
<input id="octopush-login" v-model="$parent.notification.octopushLogin" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-type-sms" class="form-label">SMS Type</label>
|
||||
<select id="octopush-type-sms" v-model="$parent.notification.octopushSMSType" class="form-select">
|
||||
<option value="sms_premium">Premium (Fast - recommended for alerting)</option>
|
||||
<option value="sms_low_cost">Low Cost (Slow, sometimes blocked by operator)</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
Check octopush prices <a href="https://octopush.com/tarifs-sms-international/" target="_blank">https://octopush.com/tarifs-sms-international/</a>.
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-phone-number" class="form-label">Phone number (intl format, eg : +33612345678) </label>
|
||||
<input id="octopush-phone-number" v-model="$parent.notification.octopushPhoneNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label for="octopush-sender-name" class="form-label">SMS Sender Name : 3-11 alphanumeric characters and space (a-zA-Z0-9)</label>
|
||||
<input id="octopush-sender-name" v-model="$parent.notification.octopushSenderName" type="text" minlength="3" maxlength="11" class="form-control">
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://octopush.com/api-sms-documentation/envoi-de-sms/" target="_blank">https://octopush.com/api-sms-documentation/envoi-de-sms/</a>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
}
|
||||
</script>
|
20
src/components/notifications/Pushbullet.vue
Normal file
20
src/components/notifications/Pushbullet.vue
Normal file
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="pushbullet-access-token" class="form-label">Access Token</label>
|
||||
<HiddenInput id="pushbullet-access-token" v-model="$parent.notification.pushbulletAccessToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://docs.pushbullet.com" target="_blank">https://docs.pushbullet.com</a>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
}
|
||||
</script>
|
67
src/components/notifications/Pushover.vue
Normal file
67
src/components/notifications/Pushover.vue
Normal file
|
@ -0,0 +1,67 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="pushover-user" class="form-label">User Key<span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="pushover-user" v-model="$parent.notification.pushoveruserkey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<label for="pushover-app-token" class="form-label">Application Token<span style="color: red;"><sup>*</sup></span></label>
|
||||
<HiddenInput id="pushover-app-token" v-model="$parent.notification.pushoverapptoken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
<label for="pushover-device" class="form-label">Device</label>
|
||||
<input id="pushover-device" v-model="$parent.notification.pushoverdevice" type="text" class="form-control">
|
||||
<label for="pushover-device" class="form-label">Message Title</label>
|
||||
<input id="pushover-title" v-model="$parent.notification.pushovertitle" type="text" class="form-control">
|
||||
<label for="pushover-priority" class="form-label">Priority</label>
|
||||
<select id="pushover-priority" v-model="$parent.notification.pushoverpriority" class="form-select">
|
||||
<option>-2</option>
|
||||
<option>-1</option>
|
||||
<option>0</option>
|
||||
<option>1</option>
|
||||
<option>2</option>
|
||||
</select>
|
||||
<label for="pushover-sound" class="form-label">Notification Sound</label>
|
||||
<select id="pushover-sound" v-model="$parent.notification.pushoversounds" class="form-select">
|
||||
<option>pushover</option>
|
||||
<option>bike</option>
|
||||
<option>bugle</option>
|
||||
<option>cashregister</option>
|
||||
<option>classical</option>
|
||||
<option>cosmic</option>
|
||||
<option>falling</option>
|
||||
<option>gamelan</option>
|
||||
<option>incoming</option>
|
||||
<option>intermission</option>
|
||||
<option>mechanical</option>
|
||||
<option>pianobar</option>
|
||||
<option>siren</option>
|
||||
<option>spacealarm</option>
|
||||
<option>tugboat</option>
|
||||
<option>alien</option>
|
||||
<option>climb</option>
|
||||
<option>persistent</option>
|
||||
<option>echo</option>
|
||||
<option>updown</option>
|
||||
<option>vibrate</option>
|
||||
<option>none</option>
|
||||
</select>
|
||||
<div class="form-text">
|
||||
<span style="color: red;"><sup>*</sup></span>Required
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://pushover.net/api" target="_blank">https://pushover.net/api</a>
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
Emergency priority (2) has default 30 second timeout between retries and will expire after 1 hour.
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
If you want to send notifications to different devices, fill out Device field.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
}
|
||||
</script>
|
26
src/components/notifications/Pushy.vue
Normal file
26
src/components/notifications/Pushy.vue
Normal file
|
@ -0,0 +1,26 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="pushy-app-token" class="form-label">API_KEY</label>
|
||||
<HiddenInput id="pushy-app-token" v-model="$parent.notification.pushyAPIKey" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="pushy-user-key" class="form-label">USER_TOKEN</label>
|
||||
<div class="input-group mb-3">
|
||||
<HiddenInput id="pushy-user-key" v-model="$parent.notification.pushyToken" :required="true" autocomplete="one-time-code"></HiddenInput>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin-top: 8px;">
|
||||
More info on: <a href="https://pushy.me/docs/api/send-notifications" target="_blank">https://pushy.me/docs/api/send-notifications</a>
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import HiddenInput from "../HiddenInput.vue";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
}
|
||||
</script>
|
29
src/components/notifications/RocketChat.vue
Normal file
29
src/components/notifications/RocketChat.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="rocket-webhook-url" class="form-label">Webhook URL<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="rocket-webhook-url" v-model="$parent.notification.rocketwebhookURL" type="text" class="form-control" required>
|
||||
<label for="rocket-username" class="form-label">Username</label>
|
||||
<input id="rocket-username" v-model="$parent.notification.rocketusername" type="text" class="form-control">
|
||||
<label for="rocket-iconemo" class="form-label">Icon Emoji</label>
|
||||
<input id="rocket-iconemo" v-model="$parent.notification.rocketiconemo" type="text" class="form-control">
|
||||
<label for="rocket-channel" class="form-label">Channel Name</label>
|
||||
<input id="rocket-channel-name" v-model="$parent.notification.rocketchannel" type="text" class="form-control">
|
||||
<label for="rocket-button-url" class="form-label">Uptime Kuma URL</label>
|
||||
<input id="rocket-button" v-model="$parent.notification.rocketbutton" 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.rocket.chat/guides/administration/administration/integrations" target="_blank">https://api.slack.com/messaging/webhooks</a>
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
Enter the channel name on Rocket.chat Channel Name field if you want to bypass the webhook channel. 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;">
|
||||
Emoji cheat sheet: <a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -66,9 +66,9 @@ export default {
|
|||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: "smtp",
|
||||
mounted() {
|
||||
if (typeof this.$parent.notification.smtpSecure === "undefined") {
|
||||
this.$parent.notification.smtpSecure = false;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
32
src/components/notifications/Signal.vue
Normal file
32
src/components/notifications/Signal.vue
Normal file
|
@ -0,0 +1,32 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="signal-url" class="form-label">Post URL</label>
|
||||
<input id="signal-url" v-model="$parent.notification.signalURL" type="url" pattern="https?://.+" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="signal-number" class="form-label">Number</label>
|
||||
<input id="signal-number" v-model="$parent.notification.signalNumber" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="signal-recipients" class="form-label">Recipients</label>
|
||||
<input id="signal-recipients" v-model="$parent.notification.signalRecipients" type="text" class="form-control" required>
|
||||
|
||||
<div class="form-text">
|
||||
You need to have a signal client with REST API.
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
You can check this url to view how to setup one:
|
||||
</p>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
<a href="https://github.com/bbernhard/signal-cli-rest-api" target="_blank">https://github.com/bbernhard/signal-cli-rest-api</a>
|
||||
</p>
|
||||
|
||||
<p style="margin-top: 8px;">
|
||||
IMPORTANT: You cannot mix groups and numbers in recipients!
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
29
src/components/notifications/Slack.vue
Normal file
29
src/components/notifications/Slack.vue
Normal file
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="slack-webhook-url" class="form-label">Webhook URL<span style="color: red;"><sup>*</sup></span></label>
|
||||
<input id="slack-webhook-url" v-model="$parent.notification.slackwebhookURL" type="text" class="form-control" required>
|
||||
<label for="slack-username" class="form-label">Username</label>
|
||||
<input id="slack-username" v-model="$parent.notification.slackusername" type="text" class="form-control">
|
||||
<label for="slack-iconemo" class="form-label">Icon Emoji</label>
|
||||
<input id="slack-iconemo" v-model="$parent.notification.slackiconemo" type="text" class="form-control">
|
||||
<label for="slack-channel" class="form-label">Channel Name</label>
|
||||
<input id="slack-channel-name" v-model="$parent.notification.slackchannel" type="text" class="form-control">
|
||||
<label for="slack-button-url" class="form-label">Uptime Kuma URL</label>
|
||||
<input id="slack-button" v-model="$parent.notification.slackbutton" 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://api.slack.com/messaging/webhooks" target="_blank">https://api.slack.com/messaging/webhooks</a>
|
||||
</p>
|
||||
<p style="margin-top: 8px;">
|
||||
Enter the channel name on Slack Channel Name field if you want to bypass the webhook channel. 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;">
|
||||
Emoji cheat sheet: <a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank">https://www.webfx.com/tools/emoji-cheat-sheet/</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -17,13 +17,3 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
name: "teams",
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -47,11 +47,6 @@ export default {
|
|||
components: {
|
||||
HiddenInput,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
name: "telegram",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
telegramGetUpdatesURL() {
|
||||
let token = "<YOUR BOT TOKEN HERE>"
|
||||
|
@ -62,9 +57,6 @@ export default {
|
|||
|
||||
return `https://api.telegram.org/bot${token}/getUpdates`;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
async autoGetTelegramChatID() {
|
||||
|
|
23
src/components/notifications/Webhook.vue
Normal file
23
src/components/notifications/Webhook.vue
Normal file
|
@ -0,0 +1,23 @@
|
|||
<template>
|
||||
<div class="mb-3">
|
||||
<label for="webhook-url" class="form-label">Post URL</label>
|
||||
<input id="webhook-url" v-model="$parent.notification.webhookURL" type="url" pattern="https?://.+" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="webhook-content-type" class="form-label">Content Type</label>
|
||||
<select id="webhook-content-type" v-model="$parent.notification.webhookContentType" class="form-select" required>
|
||||
<option value="json">
|
||||
application/json
|
||||
</option>
|
||||
<option value="form-data">
|
||||
multipart/form-data
|
||||
</option>
|
||||
</select>
|
||||
|
||||
<div class="form-text">
|
||||
<p>"application/json" is good for any modern http servers such as express.js</p>
|
||||
<p>"multipart/form-data" is good for PHP, you just need to parse the json by <strong>json_decode($_POST['data'])</strong></p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
44
src/components/notifications/index.js
Normal file
44
src/components/notifications/index.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
import STMP from "./SMTP.vue"
|
||||
import Telegram from "./Telegram.vue";
|
||||
import Discord from "./Discord.vue";
|
||||
import Webhook from "./Webhook.vue";
|
||||
import Signal from "./Signal.vue";
|
||||
import Gotify from "./Gotify.vue";
|
||||
import Slack from "./Slack.vue";
|
||||
import RocketChat from "./RocketChat.vue";
|
||||
import Teams from "./Teams.vue";
|
||||
import Pushover from "./Pushover.vue";
|
||||
import Pushy from "./Pushy.vue";
|
||||
import Octopush from "./Octopush.vue";
|
||||
import LunaSea from "./LunaSea.vue";
|
||||
import Apprise from "./Apprise.vue";
|
||||
import Pushbullet from "./Pushbullet.vue";
|
||||
import Line from "./Line.vue";
|
||||
import Mattermost from "./Mattermost.vue";
|
||||
|
||||
/**
|
||||
* Manage all notification form.
|
||||
*
|
||||
* @type { Record<string, any> }
|
||||
*/
|
||||
const NotificationFormList = {
|
||||
"telegram": Telegram,
|
||||
"webhook": Webhook,
|
||||
"smtp": STMP,
|
||||
"discord": Discord,
|
||||
"teams": Teams,
|
||||
"signal": Signal,
|
||||
"gotify": Gotify,
|
||||
"slack": Slack,
|
||||
"rocket.chat": RocketChat,
|
||||
"pushover": Pushover,
|
||||
"pushy": Pushy,
|
||||
"octopush": Octopush,
|
||||
"lunasea": LunaSea,
|
||||
"apprise": Apprise,
|
||||
"pushbullet": Pushbullet,
|
||||
"line": Line,
|
||||
"mattermost": Mattermost
|
||||
}
|
||||
|
||||
export default NotificationFormList
|
|
@ -26,7 +26,10 @@ import {
|
|||
faArrowsAltV,
|
||||
faUnlink,
|
||||
faQuestionCircle,
|
||||
faImages, faUpload,
|
||||
faImages,
|
||||
faUpload,
|
||||
faCopy,
|
||||
faCheck,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(
|
||||
|
@ -54,6 +57,8 @@ library.add(
|
|||
faQuestionCircle,
|
||||
faImages,
|
||||
faUpload,
|
||||
faCopy,
|
||||
faCheck,
|
||||
);
|
||||
|
||||
export { FontAwesomeIcon };
|
||||
|
|
|
@ -178,4 +178,21 @@ export default {
|
|||
"Add a monitor": "Add a monitor",
|
||||
"Edit Status Page": "Edit Status Page",
|
||||
"Go to Dashboard": "Go to Dashboard",
|
||||
"telegram": "Telegram",
|
||||
"webhook": "Webhook",
|
||||
"smtp": "Email (SMTP)",
|
||||
"discord": "Discord",
|
||||
"teams": "Microsoft Teams",
|
||||
"signal": "Signal",
|
||||
"gotify": "Gotify",
|
||||
"slack": "Slack",
|
||||
"rocket.chat": "Rocket.chat",
|
||||
"pushover": "Pushover",
|
||||
"pushy": "Pushy",
|
||||
"octopush": "Octopush",
|
||||
"lunasea": "LunaSea",
|
||||
"apprise": "Apprise (Support 50+ Notification services)",
|
||||
"pushbullet": "Pushbullet",
|
||||
"line": "Line Messenger",
|
||||
"mattermost": "Mattermost",
|
||||
};
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import dayjs from "dayjs";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import relativeTime from "dayjs/plugin/relativeTime";
|
||||
import timezone from "dayjs/plugin/timezone";
|
||||
import utc from "dayjs/plugin/utc";
|
||||
dayjs.extend(utc);
|
||||
dayjs.extend(timezone);
|
||||
dayjs.extend(relativeTime);
|
||||
|
@ -14,7 +14,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
userTimezone: localStorage.timezone || "auto",
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -47,11 +47,11 @@ export default {
|
|||
computed: {
|
||||
timezone() {
|
||||
if (this.userTimezone === "auto") {
|
||||
return dayjs.tz.guess()
|
||||
return dayjs.tz.guess();
|
||||
}
|
||||
|
||||
return this.userTimezone
|
||||
return this.userTimezone;
|
||||
},
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
|
|
@ -36,5 +36,13 @@ export default {
|
|||
|
||||
return result;
|
||||
},
|
||||
|
||||
baseURL() {
|
||||
if (env === "development" || localStorage.dev === "dev") {
|
||||
return axios.defaults.baseURL;
|
||||
} else {
|
||||
return location.protocol + "//" + location.host;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -23,19 +23,39 @@
|
|||
<option value="dns">
|
||||
{{ $t("DNS") }}
|
||||
</option>
|
||||
<option value="push">
|
||||
Push
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Friendly Name -->
|
||||
<div class="my-3">
|
||||
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
||||
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<!-- URL -->
|
||||
<div v-if="monitor.type === 'http'" class="my-3">
|
||||
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
||||
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
|
||||
</div>
|
||||
|
||||
<!-- Push URL -->
|
||||
<div v-if="monitor.type === 'push' " class="my-3">
|
||||
<label for="push-url" class="form-label">{{ $t("Push URL") }}</label>
|
||||
<CopyableInput id="push-url" v-model="pushURL" type="url" disabled="disabled" />
|
||||
</div>
|
||||
|
||||
<!-- Keyword -->
|
||||
<div v-if="monitor.type === 'keyword' " class="my-3">
|
||||
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
||||
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
{{ $t("keywordDescription") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- TCP Port / Ping / DNS only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||
|
@ -186,14 +206,22 @@
|
|||
<script>
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import CopyableInput from "../components/CopyableInput.vue";
|
||||
import MonitorCheckEditor from "../components/MonitorCheckEditor.vue";
|
||||
import { useToast } from "vue-toastification";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { isDev } from "../util.ts";
|
||||
const toast = useToast();
|
||||
|
||||
import { useToast } from "vue-toastification";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { genSecret, isDev } from "../util.ts";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CopyableInput,
|
||||
NotificationDialog,
|
||||
TagsManager,
|
||||
MonitorCheckEditor,
|
||||
|
@ -231,23 +259,42 @@ export default {
|
|||
pageName() {
|
||||
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
||||
},
|
||||
|
||||
isAdd() {
|
||||
return this.$route.path === "/add";
|
||||
},
|
||||
|
||||
isEdit() {
|
||||
return this.$route.path.startsWith("/edit");
|
||||
},
|
||||
|
||||
pushURL() {
|
||||
|
||||
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?msg=OK";
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
|
||||
"$route.fullPath"() {
|
||||
this.init();
|
||||
},
|
||||
|
||||
"monitor.interval"(value, oldValue) {
|
||||
// Link interval and retryInerval if they are the same value.
|
||||
if (this.monitor.retryInterval === oldValue) {
|
||||
this.monitor.retryInterval = value;
|
||||
}
|
||||
},
|
||||
|
||||
"monitor.type"() {
|
||||
if (this.monitor.type === "push") {
|
||||
if (! this.monitor.pushToken) {
|
||||
this.monitor.pushToken = genSecret(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
|
|
|
@ -90,9 +90,9 @@
|
|||
|
||||
<!-- Incident Date -->
|
||||
<div class="date mt-3">
|
||||
Created: {{ incident.createdDate }} ({{ createdDateFromNow }})<br />
|
||||
Created: {{ $root.datetime(incident.createdDate) }} ({{ dateFromNow(incident.createdDate) }})<br />
|
||||
<span v-if="incident.lastUpdatedDate">
|
||||
Last Updated: {{ incident.lastUpdatedDate }} ({{ lastUpdatedDateFromNow }})
|
||||
Last Updated: {{ $root.datetime(incident.lastUpdatedDate) }} ({{ dateFromNow(incident.lastUpdatedDate) }})
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
@ -157,7 +157,7 @@
|
|||
</div>
|
||||
|
||||
<div v-else>
|
||||
<font-awesome-icon icon="question-circle" style="color: #efefef" />
|
||||
<font-awesome-icon icon="question-circle" style="color: #efefef;" />
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
@ -343,14 +343,6 @@ export default {
|
|||
return this.overallStatus === STATUS_PAGE_ALL_DOWN;
|
||||
},
|
||||
|
||||
createdDateFromNow() {
|
||||
return dayjs.utc(this.incident.createdDate).fromNow();
|
||||
},
|
||||
|
||||
lastUpdatedDateFromNow() {
|
||||
return dayjs.utc(this.incident. lastUpdatedDate).fromNow();
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
|
||||
|
@ -548,7 +540,12 @@ export default {
|
|||
this.$root.getSocket().emit("unpinIncident", () => {
|
||||
this.incident = null;
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
dateFromNow(date) {
|
||||
return dayjs.utc(date).fromNow();
|
||||
},
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -6,9 +6,9 @@ import DashboardHome from "./pages/DashboardHome.vue";
|
|||
import Details from "./pages/Details.vue";
|
||||
import EditMonitor from "./pages/EditMonitor.vue";
|
||||
import List from "./pages/List.vue";
|
||||
import Settings from "./pages/Settings.vue";
|
||||
const Settings = () => import("./pages/Settings.vue");
|
||||
import Setup from "./pages/Setup.vue";
|
||||
import StatusPage from "./pages/StatusPage.vue";
|
||||
const StatusPage = () => import("./pages/StatusPage.vue");
|
||||
import Entry from "./pages/Entry.vue";
|
||||
|
||||
const routes = [
|
||||
|
|
12
src/util.js
12
src/util.js
|
@ -7,7 +7,7 @@
|
|||
// Backend uses the compiled file util.js
|
||||
// Frontend uses util.ts
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.MONITOR_CHECK_SELECTOR_TYPES = exports.MONITOR_CHECK_STRING_TYPES = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
|
||||
exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
|
||||
const _dayjs = require("dayjs");
|
||||
const dayjs = _dayjs;
|
||||
exports.isDev = process.env.NODE_ENV === "development";
|
||||
|
@ -104,3 +104,13 @@ function getRandomInt(min, max) {
|
|||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
exports.getRandomInt = getRandomInt;
|
||||
function genSecret(length = 64) {
|
||||
let secret = "";
|
||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let charsLength = chars.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
secret += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
exports.genSecret = genSecret;
|
||||
|
|
10
src/util.ts
10
src/util.ts
|
@ -115,3 +115,13 @@ export function getRandomInt(min: number, max: number) {
|
|||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
export function genSecret(length = 64) {
|
||||
let secret = "";
|
||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let charsLength = chars.length;
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
secret += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue