mirror of
https://github.com/louislam/uptime-kuma.git
synced 2024-12-24 23:09:39 -05:00
add Push-based monitoring (#279)
This commit is contained in:
parent
9e95d568c2
commit
1ed4ac9494
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;
|
@ -48,6 +48,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,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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,
|
||||||
tags: tags,
|
tags: tags,
|
||||||
};
|
};
|
||||||
@ -236,6 +237,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()) {
|
||||||
@ -263,6 +286,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
|
||||||
@ -312,8 +337,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) {
|
||||||
@ -339,7 +362,14 @@ class Monitor extends BeanModel {
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Delay Push Type
|
||||||
|
if (this.type === "push") {
|
||||||
|
setTimeout(() => {
|
||||||
beat();
|
beat();
|
||||||
|
}, this.interval * 1000);
|
||||||
|
} else {
|
||||||
|
beat();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
|
@ -4,15 +4,53 @@ 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;
|
||||||
|
|
||||||
|
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,7 +6,7 @@ if (! process.env.NODE_ENV) {
|
|||||||
|
|
||||||
console.log("Node Env: " + process.env.NODE_ENV);
|
console.log("Node Env: " + process.env.NODE_ENV);
|
||||||
|
|
||||||
const { sleep, debug, TimeLogger, getRandomInt } = require("../src/util");
|
const { sleep, debug, getRandomInt, genSecret } = require("../src/util");
|
||||||
|
|
||||||
console.log("Importing Node libraries");
|
console.log("Importing Node libraries");
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
@ -37,7 +37,7 @@ console.log("Importing this project modules");
|
|||||||
debug("Importing Monitor");
|
debug("Importing Monitor");
|
||||||
const Monitor = require("./model/monitor");
|
const Monitor = require("./model/monitor");
|
||||||
debug("Importing Settings");
|
debug("Importing Settings");
|
||||||
const { getSettings, setSettings, setting, initJWTSecret, genSecret, allowDevAllOrigin, checkLogin } = require("./util-server");
|
const { getSettings, setSettings, setting, initJWTSecret, checkLogin } = require("./util-server");
|
||||||
|
|
||||||
debug("Importing Notification");
|
debug("Importing Notification");
|
||||||
const { Notification } = require("./notification");
|
const { Notification } = require("./notification");
|
||||||
@ -71,7 +71,7 @@ if (demoMode) {
|
|||||||
console.log("==== Demo Mode ====");
|
console.log("==== Demo Mode ====");
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log("Creating express and socket.io instance")
|
console.log("Creating express and socket.io instance");
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
let server;
|
let server;
|
||||||
@ -511,6 +511,7 @@ exports.entryPage = "dashboard";
|
|||||||
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
|
bean.accepted_statuscodes_json = JSON.stringify(monitor.accepted_statuscodes);
|
||||||
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;
|
||||||
|
|
||||||
await R.store(bean);
|
await R.store(bean);
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -180,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;
|
||||||
|
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>
|
@ -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 };
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -26,19 +26,31 @@
|
|||||||
<option value="dns">
|
<option value="dns">
|
||||||
DNS
|
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' || monitor.type === 'keyword' " class="my-3">
|
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " 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">
|
<div v-if="monitor.type === 'keyword' " class="my-3">
|
||||||
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
||||||
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
||||||
@ -210,13 +222,17 @@
|
|||||||
<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 { useToast } from "vue-toastification"
|
import CopyableInput from "../components/CopyableInput.vue";
|
||||||
import VueMultiselect from "vue-multiselect"
|
|
||||||
import { isDev } from "../util.ts";
|
import { useToast } from "vue-toastification";
|
||||||
const toast = useToast()
|
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,
|
||||||
VueMultiselect,
|
VueMultiselect,
|
||||||
@ -236,7 +252,7 @@ export default {
|
|||||||
ipRegexPattern: "((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))",
|
ipRegexPattern: "((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))",
|
||||||
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
||||||
hostnameRegexPattern: "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
|
hostnameRegexPattern: "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
|
||||||
}
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@ -253,23 +269,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();
|
||||||
@ -320,7 +355,7 @@ export default {
|
|||||||
accepted_statuscodes: ["200-299"],
|
accepted_statuscodes: ["200-299"],
|
||||||
dns_resolve_type: "A",
|
dns_resolve_type: "A",
|
||||||
dns_resolve_server: "1.1.1.1",
|
dns_resolve_server: "1.1.1.1",
|
||||||
}
|
};
|
||||||
|
|
||||||
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
||||||
if (this.$root.notificationList[i].isDefault == true) {
|
if (this.$root.notificationList[i].isDefault == true) {
|
||||||
@ -337,9 +372,9 @@ export default {
|
|||||||
this.monitor.retryInterval = this.monitor.interval;
|
this.monitor.retryInterval = this.monitor.interval;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg)
|
toast.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
@ -356,13 +391,13 @@ export default {
|
|||||||
toast.success(res.msg);
|
toast.success(res.msg);
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.getMonitorList();
|
this.$root.getMonitorList();
|
||||||
this.$router.push("/dashboard/" + res.monitorID)
|
this.$router.push("/dashboard/" + res.monitorID);
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
});
|
||||||
} else {
|
} else {
|
||||||
await this.$refs.tagsManager.submit(this.monitor.id);
|
await this.$refs.tagsManager.submit(this.monitor.id);
|
||||||
|
|
||||||
@ -370,7 +405,7 @@ export default {
|
|||||||
this.processing = false;
|
this.processing = false;
|
||||||
this.$root.toastRes(res);
|
this.$root.toastRes(res);
|
||||||
this.init();
|
this.init();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -380,7 +415,7 @@ export default {
|
|||||||
this.monitor.notificationIDList[id] = true;
|
this.monitor.notificationIDList[id] = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
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.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";
|
||||||
@ -102,3 +102,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
@ -113,3 +113,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…
Reference in New Issue
Block a user