mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-06-15 08:56:47 +02:00
Added the ability to choose which monitors the current monitor depends on.
This commit is contained in:
parent
a9df7b4a14
commit
64c8a90e8a
16 changed files with 269 additions and 20 deletions
13
db/patch-dependent-monitors-table.sql
Normal file
13
db/patch-dependent-monitors-table.sql
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
-- You should not modify if this have pushed to Github, unless it does serious wrong with the db.
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
|
||||||
|
CREATE TABLE dependent_monitors
|
||||||
|
(
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||||
|
monitor_id INTEGER NOT NULL,
|
||||||
|
depends_on INTEGER NOT NULL,
|
||||||
|
CONSTRAINT FK_monitor_depends_on FOREIGN KEY (depends_on) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||||
|
CONSTRAINT FK_monitor_id FOREIGN KEY (monitor_id) REFERENCES monitor (id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
COMMIT;
|
|
@ -53,6 +53,7 @@ class Database {
|
||||||
"patch-2fa-invalidate-used-token.sql": true,
|
"patch-2fa-invalidate-used-token.sql": true,
|
||||||
"patch-notification_sent_history.sql": true,
|
"patch-notification_sent_history.sql": true,
|
||||||
"patch-monitor-basic-auth.sql": true,
|
"patch-monitor-basic-auth.sql": true,
|
||||||
|
"patch-dependent-monitors-table.sql": true,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -10,6 +10,7 @@ const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
* 1 = UP
|
* 1 = UP
|
||||||
* 2 = PENDING
|
* 2 = PENDING
|
||||||
|
* 4 = DEGRADED
|
||||||
*/
|
*/
|
||||||
class Heartbeat extends BeanModel {
|
class Heartbeat extends BeanModel {
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ dayjs.extend(utc);
|
||||||
dayjs.extend(timezone);
|
dayjs.extend(timezone);
|
||||||
const axios = require("axios");
|
const axios = require("axios");
|
||||||
const { Prometheus } = require("../prometheus");
|
const { Prometheus } = require("../prometheus");
|
||||||
const { debug, UP, DOWN, PENDING, flipStatus, TimeLogger } = require("../../src/util");
|
const { debug, UP, DOWN, PENDING, DEGRADED, flipStatus, TimeLogger } = require("../../src/util");
|
||||||
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server");
|
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom, setting, errorLog } = require("../util-server");
|
||||||
const { R } = require("redbean-node");
|
const { R } = require("redbean-node");
|
||||||
const { BeanModel } = require("redbean-node/dist/bean-model");
|
const { BeanModel } = require("redbean-node/dist/bean-model");
|
||||||
|
@ -20,6 +20,7 @@ const apicache = require("../modules/apicache");
|
||||||
* 0 = DOWN
|
* 0 = DOWN
|
||||||
* 1 = UP
|
* 1 = UP
|
||||||
* 2 = PENDING
|
* 2 = PENDING
|
||||||
|
* 4 = DEGRADED
|
||||||
*/
|
*/
|
||||||
class Monitor extends BeanModel {
|
class Monitor extends BeanModel {
|
||||||
|
|
||||||
|
@ -362,6 +363,11 @@ class Monitor extends BeanModel {
|
||||||
|
|
||||||
retries = 0;
|
retries = 0;
|
||||||
|
|
||||||
|
if (bean.status === UP && await Monitor.isDegraded(this.id)) {
|
||||||
|
bean.msg = "Monitor is degraded, because at least one dependent monitor is DOWN";
|
||||||
|
bean.status = DEGRADED;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
||||||
bean.msg = error.message;
|
bean.msg = error.message;
|
||||||
|
@ -387,8 +393,13 @@ class Monitor extends BeanModel {
|
||||||
if (isImportant) {
|
if (isImportant) {
|
||||||
bean.important = true;
|
bean.important = true;
|
||||||
|
|
||||||
debug(`[${this.name}] sendNotification`);
|
if (Monitor.isImportantForNotification(isFirstBeat, previousBeat?.status, bean.status)) {
|
||||||
await Monitor.sendNotification(isFirstBeat, this, bean);
|
debug(`[${this.name}] sendNotification`);
|
||||||
|
await Monitor.sendNotification(isFirstBeat, this, bean);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
debug(`[${this.name}] will not sendNotification because it is not required`);
|
||||||
|
}
|
||||||
|
|
||||||
// Clear Status Page Cache
|
// Clear Status Page Cache
|
||||||
debug(`[${this.name}] apicache clear`);
|
debug(`[${this.name}] apicache clear`);
|
||||||
|
@ -405,6 +416,8 @@ class Monitor extends BeanModel {
|
||||||
beatInterval = this.retryInterval;
|
beatInterval = this.retryInterval;
|
||||||
}
|
}
|
||||||
console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
console.warn(`Monitor #${this.id} '${this.name}': Pending: ${bean.msg} | Max retries: ${this.maxretries} | Retry: ${retries} | Retry Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
||||||
|
} else if (bean.status === DEGRADED) {
|
||||||
|
console.warn(`Monitor #${this.id} '${this.name}': Degraded: ${bean.msg} | Type: ${this.type}`);
|
||||||
} else {
|
} else {
|
||||||
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
console.warn(`Monitor #${this.id} '${this.name}': Failing: ${bean.msg} | Interval: ${beatInterval} seconds | Type: ${this.type}`);
|
||||||
}
|
}
|
||||||
|
@ -659,11 +672,42 @@ class Monitor extends BeanModel {
|
||||||
// DOWN -> PENDING = this case not exists
|
// DOWN -> PENDING = this case not exists
|
||||||
// DOWN -> DOWN = not important
|
// DOWN -> DOWN = not important
|
||||||
// * DOWN -> UP = important
|
// * DOWN -> UP = important
|
||||||
let isImportant = isFirstBeat ||
|
// * DEGRADED -> DOWN = important
|
||||||
|
// * DEGRADED -> UP = important
|
||||||
|
// * DOWN -> DEGRADED = important
|
||||||
|
// * UP -> DEGRADED = important
|
||||||
|
// DEGRADED -> PENDING = not important
|
||||||
|
return isFirstBeat ||
|
||||||
|
(previousBeatStatus === DEGRADED && currentBeatStatus === DOWN) ||
|
||||||
|
(previousBeatStatus === DEGRADED && currentBeatStatus === UP) ||
|
||||||
|
(previousBeatStatus === DOWN && currentBeatStatus === DEGRADED) ||
|
||||||
|
(previousBeatStatus === UP && currentBeatStatus === DEGRADED) ||
|
||||||
|
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
||||||
|
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
||||||
|
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
||||||
|
}
|
||||||
|
|
||||||
|
static isImportantForNotification(isFirstBeat, previousBeatStatus, currentBeatStatus) {
|
||||||
|
// * ? -> ANY STATUS = important [isFirstBeat]
|
||||||
|
// UP -> PENDING = not important
|
||||||
|
// * UP -> DOWN = important
|
||||||
|
// UP -> UP = not important
|
||||||
|
// PENDING -> PENDING = not important
|
||||||
|
// * PENDING -> DOWN = important
|
||||||
|
// PENDING -> UP = not important
|
||||||
|
// DOWN -> PENDING = this case not exists
|
||||||
|
// DOWN -> DOWN = not important
|
||||||
|
// * DOWN -> UP = important
|
||||||
|
// * DEGRADED -> DOWN = important
|
||||||
|
// DEGRADED -> UP = not important
|
||||||
|
// DOWN -> DEGRADED = not important
|
||||||
|
// UP -> DEGRADED = not important
|
||||||
|
// DEGRADED -> PENDING = not important
|
||||||
|
return isFirstBeat ||
|
||||||
|
(previousBeatStatus === DEGRADED && currentBeatStatus === DOWN) ||
|
||||||
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
(previousBeatStatus === UP && currentBeatStatus === DOWN) ||
|
||||||
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
(previousBeatStatus === DOWN && currentBeatStatus === UP) ||
|
||||||
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
(previousBeatStatus === PENDING && currentBeatStatus === DOWN);
|
||||||
return isImportant;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static async sendNotification(isFirstBeat, monitor, bean) {
|
static async sendNotification(isFirstBeat, monitor, bean) {
|
||||||
|
@ -763,6 +807,17 @@ class Monitor extends BeanModel {
|
||||||
monitorID
|
monitorID
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static async isDegraded(monitorID) {
|
||||||
|
const monitors = await R.getAll(`
|
||||||
|
SELECT hb.id FROM heartbeat hb JOIN dependent_monitors dm on hb.monitor_id = dm.depends_on JOIN (SELECT MAX(id) AS id FROM heartbeat GROUP BY monitor_id) USING (id)
|
||||||
|
WHERE dm.monitor_id = ? AND hb.status = 0
|
||||||
|
`, [
|
||||||
|
monitorID
|
||||||
|
]);
|
||||||
|
|
||||||
|
return monitors.length !== 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Monitor;
|
module.exports = Monitor;
|
||||||
|
|
|
@ -5,7 +5,7 @@ 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 dayjs = require("dayjs");
|
||||||
const { UP, flipStatus, debug } = require("../../src/util");
|
const { UP, flipStatus, debug, DEGRADED } = require("../../src/util");
|
||||||
let router = express.Router();
|
let router = express.Router();
|
||||||
|
|
||||||
let cache = apicache.middleware;
|
let cache = apicache.middleware;
|
||||||
|
@ -51,6 +51,11 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||||
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
|
duration = dayjs(bean.time).diff(dayjs(previousHeartbeat.time), "second");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (status === UP && await Monitor.isDegraded(monitor.id)) {
|
||||||
|
msg = "Monitor is degraded, because at least one dependent monitor is DOWN";
|
||||||
|
status = DEGRADED;
|
||||||
|
}
|
||||||
|
|
||||||
debug("PreviousStatus: " + previousStatus);
|
debug("PreviousStatus: " + previousStatus);
|
||||||
debug("Current Status: " + status);
|
debug("Current Status: " + status);
|
||||||
|
|
||||||
|
@ -70,7 +75,7 @@ router.get("/api/push/:pushToken", async (request, response) => {
|
||||||
ok: true,
|
ok: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (bean.important) {
|
if (Monitor.isImportantForNotification(isFirstBeat, previousStatus, status)) {
|
||||||
await Monitor.sendNotification(isFirstBeat, monitor, bean);
|
await Monitor.sendNotification(isFirstBeat, monitor, bean);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -625,6 +625,38 @@ exports.entryPage = "dashboard";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add a new dependent_monitors
|
||||||
|
socket.on("addDependentMonitors", async (monitorID, monitors, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
await R.exec("DELETE FROM dependent_monitors WHERE monitor_id = ?", [
|
||||||
|
monitorID
|
||||||
|
]);
|
||||||
|
|
||||||
|
for await (const monitor of monitors) {
|
||||||
|
let bean = R.dispense("dependent_monitors");
|
||||||
|
|
||||||
|
bean.import({
|
||||||
|
monitor_id: monitorID,
|
||||||
|
depends_on: monitor.id
|
||||||
|
});
|
||||||
|
await R.store(bean);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
msg: "Added Successfully.",
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("getMonitorList", async (callback) => {
|
socket.on("getMonitorList", async (callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
@ -665,6 +697,29 @@ exports.entryPage = "dashboard";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
socket.on("getDependentMonitors", async (monitorID, callback) => {
|
||||||
|
try {
|
||||||
|
checkLogin(socket);
|
||||||
|
|
||||||
|
console.log(`Get dependent Monitors for Monitor: ${monitorID} User ID: ${socket.userID}`);
|
||||||
|
|
||||||
|
let monitors = await R.getAll("SELECT monitor.id, monitor.name FROM dependent_monitors dm JOIN monitor ON dm.depends_on = monitor.id WHERE dm.monitor_id = ? ", [
|
||||||
|
monitorID,
|
||||||
|
]);
|
||||||
|
|
||||||
|
callback({
|
||||||
|
ok: true,
|
||||||
|
monitors,
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
callback({
|
||||||
|
ok: false,
|
||||||
|
msg: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
socket.on("getMonitorBeats", async (monitorID, period, callback) => {
|
socket.on("getMonitorBeats", async (monitorID, period, callback) => {
|
||||||
try {
|
try {
|
||||||
checkLogin(socket);
|
checkLogin(socket);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
v-for="(beat, index) in shortBeatList"
|
v-for="(beat, index) in shortBeatList"
|
||||||
:key="index"
|
:key="index"
|
||||||
class="beat"
|
class="beat"
|
||||||
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2) }"
|
:class="{ 'empty' : (beat === 0), 'down' : (beat.status === 0), 'pending' : (beat.status === 2 || beat.status === 4) }"
|
||||||
:style="beatStyle"
|
:style="beatStyle"
|
||||||
:title="getBeatTitle(beat)"
|
:title="getBeatTitle(beat)"
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default {
|
||||||
return "primary";
|
return "primary";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.status === 2) {
|
if (this.status === 2 || this.status === 4) {
|
||||||
return "warning";
|
return "warning";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,6 +38,10 @@ export default {
|
||||||
return this.$t("Pending");
|
return this.$t("Pending");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.status === 4) {
|
||||||
|
return this.$t("Degraded");
|
||||||
|
}
|
||||||
|
|
||||||
return this.$t("Unknown");
|
return this.$t("Unknown");
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default {
|
||||||
return "primary"
|
return "primary"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.lastHeartBeat.status === 2) {
|
if (this.lastHeartBeat.status === 2 || this.lastHeartBeat.status === 4) {
|
||||||
return "warning"
|
return "warning"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,10 @@ export default {
|
||||||
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
|
ignoreTLSError: "Ignore TLS/SSL error for HTTPS websites",
|
||||||
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
|
upsideDownModeDescription: "Flip the status upside down. If the service is reachable, it is DOWN.",
|
||||||
maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
maxRedirectDescription: "Maximum number of redirects to follow. Set to 0 to disable redirects.",
|
||||||
|
Degraded: "Degraded",
|
||||||
|
"Dependent Monitors": "Dependent Monitors",
|
||||||
|
"Pick Dependent Monitors...": "Pick Dependent Monitors...",
|
||||||
|
dependentMonitorsDescription: "Select the monitor(s) on which this monitor depends. If the dependent monitor(s) fails, this monitor will be affected too.",
|
||||||
acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.",
|
acceptedStatusCodesDescription: "Select status codes which are considered as a successful response.",
|
||||||
passwordNotMatchMsg: "The repeat password does not match.",
|
passwordNotMatchMsg: "The repeat password does not match.",
|
||||||
notificationDescription: "Notifications must be assigned to a monitor to function.",
|
notificationDescription: "Notifications must be assigned to a monitor to function.",
|
||||||
|
|
|
@ -317,6 +317,14 @@ export default {
|
||||||
socket.emit("deleteMonitor", monitorID, callback);
|
socket.emit("deleteMonitor", monitorID, callback);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addDependentMonitors(monitorID, monitors, callback) {
|
||||||
|
socket.emit("addDependentMonitors", monitorID, monitors, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
getDependentMonitors(monitorID, callback) {
|
||||||
|
socket.emit("getDependentMonitors", monitorID, callback);
|
||||||
|
},
|
||||||
|
|
||||||
clearData() {
|
clearData() {
|
||||||
console.log("reset heartbeat list");
|
console.log("reset heartbeat list");
|
||||||
this.heartbeatList = {};
|
this.heartbeatList = {};
|
||||||
|
@ -385,6 +393,11 @@ export default {
|
||||||
text: this.$t("Pending"),
|
text: this.$t("Pending"),
|
||||||
color: "warning",
|
color: "warning",
|
||||||
};
|
};
|
||||||
|
} else if (lastHeartBeat.status === 4) {
|
||||||
|
result[monitorID] = {
|
||||||
|
text: this.$t("Degraded"),
|
||||||
|
color: "warning",
|
||||||
|
};
|
||||||
} else {
|
} else {
|
||||||
result[monitorID] = unknown;
|
result[monitorID] = unknown;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,10 @@
|
||||||
<h3>{{ $t("Down") }}</h3>
|
<h3>{{ $t("Down") }}</h3>
|
||||||
<span class="num text-danger">{{ stats.down }}</span>
|
<span class="num text-danger">{{ stats.down }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col">
|
||||||
|
<h3>{{ $t("Degraded") }}</h3>
|
||||||
|
<span class="num text-warning">{{ stats.degraded }}</span>
|
||||||
|
</div>
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<h3>{{ $t("Unknown") }}</h3>
|
<h3>{{ $t("Unknown") }}</h3>
|
||||||
<span class="num text-secondary">{{ stats.unknown }}</span>
|
<span class="num text-secondary">{{ stats.unknown }}</span>
|
||||||
|
@ -93,6 +97,7 @@ export default {
|
||||||
let result = {
|
let result = {
|
||||||
up: 0,
|
up: 0,
|
||||||
down: 0,
|
down: 0,
|
||||||
|
degraded: 0,
|
||||||
unknown: 0,
|
unknown: 0,
|
||||||
pause: 0,
|
pause: 0,
|
||||||
};
|
};
|
||||||
|
@ -110,6 +115,8 @@ export default {
|
||||||
result.down++;
|
result.down++;
|
||||||
} else if (beat.status === 2) {
|
} else if (beat.status === 2) {
|
||||||
result.up++;
|
result.up++;
|
||||||
|
} else if (beat.status === 4) {
|
||||||
|
result.degraded++;
|
||||||
} else {
|
} else {
|
||||||
result.unknown++;
|
result.unknown++;
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,6 +154,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="shadow-box table-shadow-box">
|
||||||
|
<label for="dependent-monitors" class="form-label" style="margin-top: 20px">{{ $t("Dependent Monitors") }}</label>
|
||||||
|
<br>
|
||||||
|
<button v-for="monitor in this.dependentMonitors" class="btn btn-monitor" style="margin: 5px; cursor: auto; color: white; font-weight: bold">
|
||||||
|
{{ monitor }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseMonitor">
|
<Confirm ref="confirmPause" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="pauseMonitor">
|
||||||
{{ $t("pauseMonitorMsg") }}
|
{{ $t("pauseMonitorMsg") }}
|
||||||
</Confirm>
|
</Confirm>
|
||||||
|
@ -212,6 +220,7 @@ export default {
|
||||||
hideCount: true,
|
hideCount: true,
|
||||||
chunksNavigation: "scroll",
|
chunksNavigation: "scroll",
|
||||||
},
|
},
|
||||||
|
dependentMonitors: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -286,9 +295,19 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
|
this.init();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
init() {
|
||||||
|
this.$root.getSocket().emit("getDependentMonitors", this.$route.params.id, (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
this.dependentMonitors = Object.values(res.monitors).map(monitor => monitor.name);
|
||||||
|
} else {
|
||||||
|
toast.error(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
testNotification() {
|
testNotification() {
|
||||||
this.$root.getSocket().emit("testNotification", this.monitor.id);
|
this.$root.getSocket().emit("testNotification", this.monitor.id);
|
||||||
toast.success("Test notification is requested.");
|
toast.success("Test notification is requested.");
|
||||||
|
@ -499,4 +518,8 @@ table {
|
||||||
margin-left: 0 !important;
|
margin-left: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-monitor {
|
||||||
|
background-color: #5cdd8b;
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -189,6 +189,32 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- Dependent Monitors -->
|
||||||
|
<div class="my-3">
|
||||||
|
<label for="dependent-monitors" class="form-label">{{ $t("Dependent Monitors") }}</label>
|
||||||
|
|
||||||
|
<VueMultiselect
|
||||||
|
id="dependent-monitors"
|
||||||
|
v-model="dependentMonitors"
|
||||||
|
:options="dependentMonitorsOptions"
|
||||||
|
track-by="id"
|
||||||
|
label="name"
|
||||||
|
:multiple="true"
|
||||||
|
:allow-empty="false"
|
||||||
|
:close-on-select="false"
|
||||||
|
:clear-on-select="false"
|
||||||
|
:preserve-search="true"
|
||||||
|
:placeholder="$t('Pick Dependent Monitors...')"
|
||||||
|
:preselect-first="false"
|
||||||
|
:max-height="600"
|
||||||
|
:taggable="false"
|
||||||
|
></VueMultiselect>
|
||||||
|
|
||||||
|
<div class="form-text">
|
||||||
|
{{ $t("dependentMonitorsDescription") }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="my-3">
|
<div class="my-3">
|
||||||
<tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager>
|
<tags-manager ref="tagsManager" :pre-selected-tags="monitor.tags"></tags-manager>
|
||||||
</div>
|
</div>
|
||||||
|
@ -317,6 +343,8 @@ export default {
|
||||||
},
|
},
|
||||||
acceptedStatusCodeOptions: [],
|
acceptedStatusCodeOptions: [],
|
||||||
dnsresolvetypeOptions: [],
|
dnsresolvetypeOptions: [],
|
||||||
|
dependentMonitors: [],
|
||||||
|
dependentMonitorsOptions: [],
|
||||||
|
|
||||||
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
// Source: https://digitalfortress.tech/tips/top-15-commonly-used-regex/
|
||||||
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*$))",
|
||||||
|
@ -392,6 +420,17 @@ export default {
|
||||||
mounted() {
|
mounted() {
|
||||||
this.init();
|
this.init();
|
||||||
|
|
||||||
|
this.$root.getMonitorList((res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
Object.values(this.$root.monitorList).filter(monitor => monitor.id != this.$route.params.id).map(monitor => {
|
||||||
|
this.dependentMonitorsOptions.push({
|
||||||
|
id: monitor.id,
|
||||||
|
name: monitor.name,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let acceptedStatusCodeOptions = [
|
let acceptedStatusCodeOptions = [
|
||||||
"100-199",
|
"100-199",
|
||||||
"200-299",
|
"200-299",
|
||||||
|
@ -422,6 +461,8 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
init() {
|
init() {
|
||||||
|
this.dependentMonitors = [];
|
||||||
|
|
||||||
if (this.isAdd) {
|
if (this.isAdd) {
|
||||||
|
|
||||||
this.monitor = {
|
this.monitor = {
|
||||||
|
@ -451,6 +492,16 @@ export default {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
this.monitor = res.monitor;
|
this.monitor = res.monitor;
|
||||||
|
|
||||||
|
this.$root.getSocket().emit("getDependentMonitors", this.$route.params.id, (res) => {
|
||||||
|
if (res.ok) {
|
||||||
|
Object.values(res.monitors).map(monitor => {
|
||||||
|
this.dependentMonitors.push(monitor);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.error(res.msg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Handling for monitors that are created before 1.7.0
|
// Handling for monitors that are created before 1.7.0
|
||||||
if (this.monitor.retryInterval === 0) {
|
if (this.monitor.retryInterval === 0) {
|
||||||
this.monitor.retryInterval = this.monitor.interval;
|
this.monitor.retryInterval = this.monitor.interval;
|
||||||
|
@ -506,10 +557,12 @@ export default {
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
await this.$refs.tagsManager.submit(res.monitorID);
|
await this.$refs.tagsManager.submit(res.monitorID);
|
||||||
|
|
||||||
toast.success(res.msg);
|
await this.addDependentMonitors(res.monitorID, () => {
|
||||||
this.processing = false;
|
toast.success(res.msg);
|
||||||
this.$root.getMonitorList();
|
this.processing = false;
|
||||||
this.$router.push("/dashboard/" + res.monitorID);
|
this.$root.getMonitorList();
|
||||||
|
this.$router.push("/dashboard/" + res.monitorID);
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.error(res.msg);
|
toast.error(res.msg);
|
||||||
this.processing = false;
|
this.processing = false;
|
||||||
|
@ -519,14 +572,27 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
await this.$refs.tagsManager.submit(this.monitor.id);
|
await this.$refs.tagsManager.submit(this.monitor.id);
|
||||||
|
|
||||||
this.$root.getSocket().emit("editMonitor", this.monitor, (res) => {
|
this.$root.getSocket().emit("editMonitor", this.monitor, async (res) => {
|
||||||
this.processing = false;
|
await this.addDependentMonitors(this.monitor.id, () => {
|
||||||
this.$root.toastRes(res);
|
this.processing = false;
|
||||||
this.init();
|
this.$root.toastRes(res);
|
||||||
|
this.init();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async addDependentMonitors(monitorID, callback) {
|
||||||
|
await this.$root.addDependentMonitors(monitorID, this.dependentMonitors, async (res) => {
|
||||||
|
if (!res.ok) {
|
||||||
|
toast.error(res.msg);
|
||||||
|
} else {
|
||||||
|
this.$root.getMonitorList();
|
||||||
|
}
|
||||||
|
callback();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
// Added a Notification Event
|
// Added a Notification Event
|
||||||
// Enable it if the notification is added in EditMonitor.vue
|
// Enable it if the notification is added in EditMonitor.vue
|
||||||
addedNotification(id) {
|
addedNotification(id) {
|
||||||
|
|
|
@ -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.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = 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.getMonitorRelativeURL = exports.genSecret = exports.getCryptoRandomInt = 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.DEGRADED = 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";
|
||||||
|
@ -15,6 +15,7 @@ exports.appName = "Uptime Kuma";
|
||||||
exports.DOWN = 0;
|
exports.DOWN = 0;
|
||||||
exports.UP = 1;
|
exports.UP = 1;
|
||||||
exports.PENDING = 2;
|
exports.PENDING = 2;
|
||||||
|
exports.DEGRADED = 4;
|
||||||
exports.STATUS_PAGE_ALL_DOWN = 0;
|
exports.STATUS_PAGE_ALL_DOWN = 0;
|
||||||
exports.STATUS_PAGE_ALL_UP = 1;
|
exports.STATUS_PAGE_ALL_UP = 1;
|
||||||
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
exports.STATUS_PAGE_PARTIAL_DOWN = 2;
|
||||||
|
|
|
@ -14,6 +14,7 @@ export const appName = "Uptime Kuma";
|
||||||
export const DOWN = 0;
|
export const DOWN = 0;
|
||||||
export const UP = 1;
|
export const UP = 1;
|
||||||
export const PENDING = 2;
|
export const PENDING = 2;
|
||||||
|
export const DEGRADED = 4;
|
||||||
|
|
||||||
export const STATUS_PAGE_ALL_DOWN = 0;
|
export const STATUS_PAGE_ALL_DOWN = 0;
|
||||||
export const STATUS_PAGE_ALL_UP = 1;
|
export const STATUS_PAGE_ALL_UP = 1;
|
||||||
|
|
Loading…
Add table
Reference in a new issue