Added the ability to choose which monitors the current monitor depends on.

This commit is contained in:
Karel Krýda 2022-01-30 15:53:22 +01:00
parent a9df7b4a14
commit 64c8a90e8a
16 changed files with 269 additions and 20 deletions

View 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;

View file

@ -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,
} }
/** /**

View file

@ -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 {

View file

@ -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;
if (Monitor.isImportantForNotification(isFirstBeat, previousBeat?.status, bean.status)) {
debug(`[${this.name}] sendNotification`); debug(`[${this.name}] sendNotification`);
await Monitor.sendNotification(isFirstBeat, this, bean); 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;

View file

@ -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);
} }

View file

@ -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);

View file

@ -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)"
/> />

View file

@ -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");
}, },
}, },

View file

@ -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"
} }

View file

@ -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.",

View file

@ -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;
} }

View file

@ -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++;
} }

View file

@ -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>

View file

@ -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);
await this.addDependentMonitors(res.monitorID, () => {
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;
@ -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) => {
await this.addDependentMonitors(this.monitor.id, () => {
this.processing = false; this.processing = false;
this.$root.toastRes(res); this.$root.toastRes(res);
this.init(); 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) {

View file

@ -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;

View file

@ -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;