mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-07 21:42:34 +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
|
# 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%">
|
<div align="center" width="100%">
|
||||||
<img src="./public/icon.svg" width="128" alt="" />
|
<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
|
FROM release AS nightly
|
||||||
RUN npm run mark-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",
|
"name": "uptime-kuma",
|
||||||
"version": "1.7.1",
|
"version": "1.7.3",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
@ -23,12 +23,13 @@
|
||||||
"tsc": "tsc",
|
"tsc": "tsc",
|
||||||
"vite-preview-dist": "vite preview --host",
|
"vite-preview-dist": "vite preview --host",
|
||||||
"build-docker": "npm run build-docker-debian && npm run build-docker-alpine",
|
"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-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.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-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": "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-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",
|
"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",
|
"update-version": "node extra/update-version.js",
|
||||||
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
"mark-as-nightly": "node extra/mark-as-nightly.js",
|
||||||
"reset-password": "node extra/reset-password.js",
|
"reset-password": "node extra/reset-password.js",
|
||||||
|
@ -104,6 +105,6 @@
|
||||||
"stylelint": "^13.13.1",
|
"stylelint": "^13.13.1",
|
||||||
"stylelint-config-standard": "^22.0.0",
|
"stylelint-config-standard": "^22.0.0",
|
||||||
"typescript": "^4.4.3",
|
"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-add-retry-interval-monitor.sql": true,
|
||||||
"patch-incident-table.sql": true,
|
"patch-incident-table.sql": true,
|
||||||
"patch-group-table.sql": true,
|
"patch-group-table.sql": true,
|
||||||
|
"patch-monitor-push_token.sql": true,
|
||||||
"patch-add-monitor-checks-table.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_type: this.dns_resolve_type,
|
||||||
dns_resolve_server: this.dns_resolve_server,
|
dns_resolve_server: this.dns_resolve_server,
|
||||||
dns_last_result: this.dns_last_result,
|
dns_last_result: this.dns_last_result,
|
||||||
|
pushToken: this.pushToken,
|
||||||
notificationIDList,
|
notificationIDList,
|
||||||
checks: checks,
|
checks: checks,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
|
@ -232,6 +233,28 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
bean.msg = dnsMessage;
|
bean.msg = dnsMessage;
|
||||||
bean.status = UP;
|
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()) {
|
if (this.isUpsideDown()) {
|
||||||
|
@ -259,6 +282,8 @@ class Monitor extends BeanModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let beatInterval = this.interval;
|
||||||
|
|
||||||
// * ? -> ANY STATUS = important [isFirstBeat]
|
// * ? -> ANY STATUS = important [isFirstBeat]
|
||||||
// UP -> PENDING = not important
|
// UP -> PENDING = not important
|
||||||
// * UP -> DOWN = important
|
// * UP -> DOWN = important
|
||||||
|
@ -308,8 +333,6 @@ class Monitor extends BeanModel {
|
||||||
bean.important = false;
|
bean.important = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let beatInterval = this.interval;
|
|
||||||
|
|
||||||
if (bean.status === UP) {
|
if (bean.status === UP) {
|
||||||
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
console.info(`Monitor #${this.id} '${this.name}': Successful Response: ${bean.ping} ms | Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
||||||
} else if (bean.status === PENDING) {
|
} 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() {
|
stop() {
|
||||||
|
|
|
@ -4,15 +4,56 @@ const { R } = require("redbean-node");
|
||||||
const server = require("../server");
|
const server = require("../server");
|
||||||
const apicache = require("../modules/apicache");
|
const apicache = require("../modules/apicache");
|
||||||
const Monitor = require("../model/monitor");
|
const Monitor = require("../model/monitor");
|
||||||
|
const dayjs = require("dayjs");
|
||||||
|
const { UP } = require("../../src/util");
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
|
|
||||||
let cache = apicache.middleware;
|
let cache = apicache.middleware;
|
||||||
|
let io = server.io;
|
||||||
|
|
||||||
router.get("/api/entry-page", async (_, response) => {
|
router.get("/api/entry-page", async (_, response) => {
|
||||||
allowDevAllOrigin(response);
|
allowDevAllOrigin(response);
|
||||||
response.json(server.entryPage);
|
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
|
// Status Page Config
|
||||||
router.get("/api/status-page/config", async (_request, response) => {
|
router.get("/api/status-page/config", async (_request, response) => {
|
||||||
allowDevAllOrigin(response);
|
allowDevAllOrigin(response);
|
||||||
|
|
|
@ -6,11 +6,7 @@ if (!process.env.NODE_ENV) {
|
||||||
|
|
||||||
console.log("Node Env: " + process.env.NODE_ENV);
|
console.log("Node Env: " + process.env.NODE_ENV);
|
||||||
|
|
||||||
const {
|
const { sleep, debug, getRandomInt, genSecret } = require("../src/util");
|
||||||
sleep,
|
|
||||||
debug,
|
|
||||||
getRandomInt,
|
|
||||||
} = require("../src/util");
|
|
||||||
|
|
||||||
console.log("Importing Node libraries");
|
console.log("Importing Node libraries");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
@ -46,8 +42,6 @@ const {
|
||||||
setSettings,
|
setSettings,
|
||||||
setting,
|
setting,
|
||||||
initJWTSecret,
|
initJWTSecret,
|
||||||
genSecret,
|
|
||||||
allowDevAllOrigin,
|
|
||||||
checkLogin,
|
checkLogin,
|
||||||
updateMonitorChecks,
|
updateMonitorChecks,
|
||||||
} = require("./util-server");
|
} = require("./util-server");
|
||||||
|
@ -320,6 +314,12 @@ exports.entryPage = "dashboard";
|
||||||
if (user.twofa_status == 0) {
|
if (user.twofa_status == 0) {
|
||||||
let newSecret = await genSecret();
|
let newSecret = await genSecret();
|
||||||
let encodedSecret = base32.encode(newSecret);
|
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}`;
|
let uri = `otpauth://totp/Uptime%20Kuma:${user.username}?secret=${encodedSecret}`;
|
||||||
|
|
||||||
await R.exec("UPDATE `user` SET twofa_secret = ? WHERE id = ? ", [
|
await R.exec("UPDATE `user` SET twofa_secret = ? WHERE id = ? ", [
|
||||||
|
@ -529,6 +529,7 @@ exports.entryPage = "dashboard";
|
||||||
bean.maxredirects = monitor.maxredirects;
|
bean.maxredirects = monitor.maxredirects;
|
||||||
bean.dns_resolve_type = monitor.dns_resolve_type;
|
bean.dns_resolve_type = monitor.dns_resolve_type;
|
||||||
bean.dns_resolve_server = monitor.dns_resolve_server;
|
bean.dns_resolve_server = monitor.dns_resolve_server;
|
||||||
|
bean.pushToken = monitor.pushToken;
|
||||||
|
|
||||||
const checks = monitor.checks;
|
const checks = monitor.checks;
|
||||||
delete 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) => {
|
exports.allowDevAllOrigin = (res) => {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
exports.allowAllOrigin(res);
|
exports.allowAllOrigin(res);
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@import "vars.scss";
|
@import "vars.scss";
|
||||||
|
@import "multiselect.scss";
|
||||||
@import "node_modules/bootstrap/scss/bootstrap";
|
@import "node_modules/bootstrap/scss/bootstrap";
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
@ -179,6 +180,11 @@ h2 {
|
||||||
border-color: $dark-border-color;
|
border-color: $dark-border-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.form-control:disabled, .form-control[readonly] {
|
||||||
|
background-color: #232f3b;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.table-hover > tbody > tr:hover {
|
.table-hover > tbody > tr:hover {
|
||||||
--bs-table-accent-bg: #070a10;
|
--bs-table-accent-bg: #070a10;
|
||||||
color: $dark-font-color;
|
color: $dark-font-color;
|
||||||
|
@ -233,30 +239,6 @@ h2 {
|
||||||
color: $dark-font-color;
|
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 {
|
.monitor-list {
|
||||||
.item {
|
.item {
|
||||||
&:hover {
|
&: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">
|
<div class="mb-3">
|
||||||
<label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label>
|
<label for="notification-type" class="form-label">{{ $t("Notification Type") }}</label>
|
||||||
<select id="notification-type" v-model="notification.type" class="form-select">
|
<select id="notification-type" v-model="notification.type" class="form-select">
|
||||||
<option value="telegram">Telegram</option>
|
<option v-for="type in notificationTypes" :key="type" :value="type">{{ $t(type) }}</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>
|
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -38,370 +22,8 @@
|
||||||
<input id="notification-name" v-model="notification.name" type="text" class="form-control" required>
|
<input id="notification-name" v-model="notification.name" type="text" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Telegram v-if="notification.type === 'telegram'" />
|
<!-- form body -->
|
||||||
|
<component :is="currentForm" />
|
||||||
<!-- 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'" />
|
|
||||||
|
|
||||||
<div class="mb-3 mt-4">
|
<div class="mb-3 mt-4">
|
||||||
<hr class="dropdown-divider mb-4">
|
<hr class="dropdown-divider mb-4">
|
||||||
|
@ -450,18 +72,11 @@ import { Modal } from "bootstrap"
|
||||||
import { ucfirst } from "../util.ts"
|
import { ucfirst } from "../util.ts"
|
||||||
|
|
||||||
import Confirm from "./Confirm.vue";
|
import Confirm from "./Confirm.vue";
|
||||||
import HiddenInput from "./HiddenInput.vue";
|
import NotificationFormList from "./notifications"
|
||||||
import Telegram from "./notifications/Telegram.vue";
|
|
||||||
import Teams from "./notifications/Teams.vue";
|
|
||||||
import SMTP from "./notifications/SMTP.vue";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Confirm,
|
Confirm,
|
||||||
HiddenInput,
|
|
||||||
Telegram,
|
|
||||||
Teams,
|
|
||||||
SMTP,
|
|
||||||
},
|
},
|
||||||
props: {},
|
props: {},
|
||||||
emits: ["added"],
|
emits: ["added"],
|
||||||
|
@ -470,13 +85,23 @@ export default {
|
||||||
model: null,
|
model: null,
|
||||||
processing: false,
|
processing: false,
|
||||||
id: null,
|
id: null,
|
||||||
|
notificationTypes: Object.keys(NotificationFormList),
|
||||||
notification: {
|
notification: {
|
||||||
name: "",
|
name: "",
|
||||||
|
/** @type { null | keyof NotificationFormList } */
|
||||||
type: null,
|
type: null,
|
||||||
isDefault: false,
|
isDefault: false,
|
||||||
// Do not set default value here, please scroll to show()
|
// 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() {
|
mounted() {
|
||||||
this.modal = new Modal(this.$refs.modal)
|
this.modal = new Modal(this.$refs.modal)
|
||||||
|
|
||||||
this.$root.getSocket().emit("checkApprise", (installed) => {
|
|
||||||
this.appriseInstalled = installed;
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
||||||
|
@ -528,9 +149,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set Default value here
|
// Set Default value here
|
||||||
this.notification.type = "telegram";
|
this.notification.type = this.notificationTypes[0];
|
||||||
this.notification.gotifyPriority = 8;
|
|
||||||
this.notification.smtpSecure = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.modal.show()
|
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: {
|
components: {
|
||||||
HiddenInput,
|
HiddenInput,
|
||||||
},
|
},
|
||||||
data() {
|
mounted() {
|
||||||
return {
|
if (typeof this.$parent.notification.smtpSecure === "undefined") {
|
||||||
name: "smtp",
|
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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
|
||||||
export default {
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
name: "teams",
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
|
@ -47,11 +47,6 @@ export default {
|
||||||
components: {
|
components: {
|
||||||
HiddenInput,
|
HiddenInput,
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
name: "telegram",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
telegramGetUpdatesURL() {
|
telegramGetUpdatesURL() {
|
||||||
let token = "<YOUR BOT TOKEN HERE>"
|
let token = "<YOUR BOT TOKEN HERE>"
|
||||||
|
@ -62,9 +57,6 @@ export default {
|
||||||
|
|
||||||
return `https://api.telegram.org/bot${token}/getUpdates`;
|
return `https://api.telegram.org/bot${token}/getUpdates`;
|
||||||
},
|
},
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async autoGetTelegramChatID() {
|
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,
|
faArrowsAltV,
|
||||||
faUnlink,
|
faUnlink,
|
||||||
faQuestionCircle,
|
faQuestionCircle,
|
||||||
faImages, faUpload,
|
faImages,
|
||||||
|
faUpload,
|
||||||
|
faCopy,
|
||||||
|
faCheck,
|
||||||
} from "@fortawesome/free-solid-svg-icons";
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
|
||||||
library.add(
|
library.add(
|
||||||
|
@ -54,6 +57,8 @@ library.add(
|
||||||
faQuestionCircle,
|
faQuestionCircle,
|
||||||
faImages,
|
faImages,
|
||||||
faUpload,
|
faUpload,
|
||||||
|
faCopy,
|
||||||
|
faCheck,
|
||||||
);
|
);
|
||||||
|
|
||||||
export { FontAwesomeIcon };
|
export { FontAwesomeIcon };
|
||||||
|
|
|
@ -178,4 +178,21 @@ export default {
|
||||||
"Add a monitor": "Add a monitor",
|
"Add a monitor": "Add a monitor",
|
||||||
"Edit Status Page": "Edit Status Page",
|
"Edit Status Page": "Edit Status Page",
|
||||||
"Go to Dashboard": "Go to Dashboard",
|
"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 dayjs from "dayjs";
|
||||||
import utc from "dayjs/plugin/utc";
|
|
||||||
import timezone from "dayjs/plugin/timezone";
|
|
||||||
import relativeTime from "dayjs/plugin/relativeTime";
|
import relativeTime from "dayjs/plugin/relativeTime";
|
||||||
|
import timezone from "dayjs/plugin/timezone";
|
||||||
|
import utc from "dayjs/plugin/utc";
|
||||||
dayjs.extend(utc);
|
dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
dayjs.extend(relativeTime);
|
dayjs.extend(relativeTime);
|
||||||
|
@ -14,7 +14,7 @@ export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
userTimezone: localStorage.timezone || "auto",
|
userTimezone: localStorage.timezone || "auto",
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -47,11 +47,11 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
timezone() {
|
timezone() {
|
||||||
if (this.userTimezone === "auto") {
|
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;
|
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">
|
<option value="dns">
|
||||||
{{ $t("DNS") }}
|
{{ $t("DNS") }}
|
||||||
</option>
|
</option>
|
||||||
|
<option value="push">
|
||||||
|
Push
|
||||||
|
</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 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>
|
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- URL -->
|
||||||
<div v-if="monitor.type === 'http'" class="my-3">
|
<div v-if="monitor.type === 'http'" class="my-3">
|
||||||
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
||||||
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
|
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
|
||||||
</div>
|
</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 -->
|
<!-- TCP Port / Ping / DNS only -->
|
||||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' " class="my-3">
|
||||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||||
|
@ -186,14 +206,22 @@
|
||||||
<script>
|
<script>
|
||||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||||
import TagsManager from "../components/TagsManager.vue";
|
import TagsManager from "../components/TagsManager.vue";
|
||||||
|
import CopyableInput from "../components/CopyableInput.vue";
|
||||||
import MonitorCheckEditor from "../components/MonitorCheckEditor.vue";
|
import MonitorCheckEditor from "../components/MonitorCheckEditor.vue";
|
||||||
import { useToast } from "vue-toastification";
|
import { useToast } from "vue-toastification";
|
||||||
import VueMultiselect from "vue-multiselect";
|
import VueMultiselect from "vue-multiselect";
|
||||||
import { isDev } from "../util.ts";
|
import { isDev } from "../util.ts";
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
|
import { useToast } from "vue-toastification";
|
||||||
|
import VueMultiselect from "vue-multiselect";
|
||||||
|
import { genSecret, isDev } from "../util.ts";
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
CopyableInput,
|
||||||
NotificationDialog,
|
NotificationDialog,
|
||||||
TagsManager,
|
TagsManager,
|
||||||
MonitorCheckEditor,
|
MonitorCheckEditor,
|
||||||
|
@ -231,23 +259,42 @@ export default {
|
||||||
pageName() {
|
pageName() {
|
||||||
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
||||||
},
|
},
|
||||||
|
|
||||||
isAdd() {
|
isAdd() {
|
||||||
return this.$route.path === "/add";
|
return this.$route.path === "/add";
|
||||||
},
|
},
|
||||||
|
|
||||||
isEdit() {
|
isEdit() {
|
||||||
return this.$route.path.startsWith("/edit");
|
return this.$route.path.startsWith("/edit");
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pushURL() {
|
||||||
|
|
||||||
|
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?msg=OK";
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
"$route.fullPath"() {
|
"$route.fullPath"() {
|
||||||
this.init();
|
this.init();
|
||||||
},
|
},
|
||||||
|
|
||||||
"monitor.interval"(value, oldValue) {
|
"monitor.interval"(value, oldValue) {
|
||||||
// Link interval and retryInerval if they are the same value.
|
// Link interval and retryInerval if they are the same value.
|
||||||
if (this.monitor.retryInterval === oldValue) {
|
if (this.monitor.retryInterval === oldValue) {
|
||||||
this.monitor.retryInterval = value;
|
this.monitor.retryInterval = value;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"monitor.type"() {
|
||||||
|
if (this.monitor.type === "push") {
|
||||||
|
if (! this.monitor.pushToken) {
|
||||||
|
this.monitor.pushToken = genSecret(10);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
|
|
|
@ -90,9 +90,9 @@
|
||||||
|
|
||||||
<!-- Incident Date -->
|
<!-- Incident Date -->
|
||||||
<div class="date mt-3">
|
<div class="date mt-3">
|
||||||
Created: {{ incident.createdDate }} ({{ createdDateFromNow }})<br />
|
Created: {{ $root.datetime(incident.createdDate) }} ({{ dateFromNow(incident.createdDate) }})<br />
|
||||||
<span v-if="incident.lastUpdatedDate">
|
<span v-if="incident.lastUpdatedDate">
|
||||||
Last Updated: {{ incident.lastUpdatedDate }} ({{ lastUpdatedDateFromNow }})
|
Last Updated: {{ $root.datetime(incident.lastUpdatedDate) }} ({{ dateFromNow(incident.lastUpdatedDate) }})
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -157,7 +157,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<font-awesome-icon icon="question-circle" style="color: #efefef" />
|
<font-awesome-icon icon="question-circle" style="color: #efefef;" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
@ -343,14 +343,6 @@ export default {
|
||||||
return this.overallStatus === STATUS_PAGE_ALL_DOWN;
|
return this.overallStatus === STATUS_PAGE_ALL_DOWN;
|
||||||
},
|
},
|
||||||
|
|
||||||
createdDateFromNow() {
|
|
||||||
return dayjs.utc(this.incident.createdDate).fromNow();
|
|
||||||
},
|
|
||||||
|
|
||||||
lastUpdatedDateFromNow() {
|
|
||||||
return dayjs.utc(this.incident. lastUpdatedDate).fromNow();
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
||||||
|
@ -548,7 +540,12 @@ export default {
|
||||||
this.$root.getSocket().emit("unpinIncident", () => {
|
this.$root.getSocket().emit("unpinIncident", () => {
|
||||||
this.incident = null;
|
this.incident = null;
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
dateFromNow(date) {
|
||||||
|
return dayjs.utc(date).fromNow();
|
||||||
|
},
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -6,9 +6,9 @@ import DashboardHome from "./pages/DashboardHome.vue";
|
||||||
import Details from "./pages/Details.vue";
|
import Details from "./pages/Details.vue";
|
||||||
import EditMonitor from "./pages/EditMonitor.vue";
|
import EditMonitor from "./pages/EditMonitor.vue";
|
||||||
import List from "./pages/List.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 Setup from "./pages/Setup.vue";
|
||||||
import StatusPage from "./pages/StatusPage.vue";
|
const StatusPage = () => import("./pages/StatusPage.vue");
|
||||||
import Entry from "./pages/Entry.vue";
|
import Entry from "./pages/Entry.vue";
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
|
|
12
src/util.js
12
src/util.js
|
@ -7,7 +7,7 @@
|
||||||
// Backend uses the compiled file util.js
|
// Backend uses the compiled file util.js
|
||||||
// Frontend uses util.ts
|
// Frontend uses util.ts
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
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 = require("dayjs");
|
||||||
const dayjs = _dayjs;
|
const dayjs = _dayjs;
|
||||||
exports.isDev = process.env.NODE_ENV === "development";
|
exports.isDev = process.env.NODE_ENV === "development";
|
||||||
|
@ -104,3 +104,13 @@ function getRandomInt(min, max) {
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||||
}
|
}
|
||||||
exports.getRandomInt = getRandomInt;
|
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);
|
max = Math.floor(max);
|
||||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
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