[status page] minor

This commit is contained in:
Louis 2021-09-17 14:42:19 +08:00
parent 9b8f01cfc6
commit a6fdd272a6
3 changed files with 63 additions and 39 deletions

View file

@ -1,16 +1,16 @@
const https = require("https"); const https = require("https");
const dayjs = require("dayjs"); const dayjs = require("dayjs");
const utc = require("dayjs/plugin/utc") const utc = require("dayjs/plugin/utc");
let timezone = require("dayjs/plugin/timezone") let timezone = require("dayjs/plugin/timezone");
dayjs.extend(utc) 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, flipStatus, TimeLogger } = require("../../src/util");
const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom } = require("../util-server"); const { tcping, ping, dnsResolve, checkCertificate, checkStatusCode, getTotalClientInRoom } = 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");
const { Notification } = require("../notification") const { Notification } = require("../notification");
const version = require("../../package.json").version; const version = require("../../package.json").version;
/** /**
@ -39,7 +39,7 @@ class Monitor extends BeanModel {
let list = await R.find("monitor_notification", " monitor_id = ? ", [ let list = await R.find("monitor_notification", " monitor_id = ? ", [
this.id, this.id,
]) ]);
for (let bean of list) { for (let bean of list) {
notificationIDList[bean.notification_id] = true; notificationIDList[bean.notification_id] = true;
@ -77,7 +77,7 @@ class Monitor extends BeanModel {
* @returns {boolean} * @returns {boolean}
*/ */
getIgnoreTls() { getIgnoreTls() {
return Boolean(this.ignoreTls) return Boolean(this.ignoreTls);
} }
/** /**
@ -107,12 +107,12 @@ class Monitor extends BeanModel {
if (! previousBeat) { if (! previousBeat) {
previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [ previousBeat = await R.findOne("heartbeat", " monitor_id = ? ORDER BY time DESC", [
this.id, this.id,
]) ]);
} }
const isFirstBeat = !previousBeat; const isFirstBeat = !previousBeat;
let bean = R.dispense("heartbeat") let bean = R.dispense("heartbeat");
bean.monitor_id = this.id; bean.monitor_id = this.id;
bean.time = R.isoDateTime(dayjs.utc()); bean.time = R.isoDateTime(dayjs.utc());
bean.status = DOWN; bean.status = DOWN;
@ -148,7 +148,7 @@ class Monitor extends BeanModel {
return checkStatusCode(status, this.getAcceptedStatuscodes()); return checkStatusCode(status, this.getAcceptedStatuscodes());
}, },
}); });
bean.msg = `${res.status} - ${res.statusText}` bean.msg = `${res.status} - ${res.statusText}`;
bean.ping = dayjs().valueOf() - startTime; bean.ping = dayjs().valueOf() - startTime;
// Check certificate if https is used // Check certificate if https is used
@ -158,12 +158,12 @@ class Monitor extends BeanModel {
tlsInfo = await this.updateTlsInfo(checkCertificate(res)); tlsInfo = await this.updateTlsInfo(checkCertificate(res));
} catch (e) { } catch (e) {
if (e.message !== "No TLS certificate in response") { if (e.message !== "No TLS certificate in response") {
console.error(e.message) console.error(e.message);
} }
} }
} }
debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms") debug("Cert Info Query Time: " + (dayjs().valueOf() - certInfoStartTime) + "ms");
if (this.type === "http") { if (this.type === "http") {
bean.status = UP; bean.status = UP;
@ -173,26 +173,26 @@ class Monitor extends BeanModel {
// Convert to string for object/array // Convert to string for object/array
if (typeof data !== "string") { if (typeof data !== "string") {
data = JSON.stringify(data) data = JSON.stringify(data);
} }
if (data.includes(this.keyword)) { if (data.includes(this.keyword)) {
bean.msg += ", keyword is found" bean.msg += ", keyword is found";
bean.status = UP; bean.status = UP;
} else { } else {
throw new Error(bean.msg + ", but keyword is not found") throw new Error(bean.msg + ", but keyword is not found");
} }
} }
} else if (this.type === "port") { } else if (this.type === "port") {
bean.ping = await tcping(this.hostname, this.port); bean.ping = await tcping(this.hostname, this.port);
bean.msg = "" bean.msg = "";
bean.status = UP; bean.status = UP;
} else if (this.type === "ping") { } else if (this.type === "ping") {
bean.ping = await ping(this.hostname); bean.ping = await ping(this.hostname);
bean.msg = "" bean.msg = "";
bean.status = UP; bean.status = UP;
} else if (this.type === "dns") { } else if (this.type === "dns") {
let startTime = dayjs().valueOf(); let startTime = dayjs().valueOf();
@ -212,7 +212,7 @@ class Monitor extends BeanModel {
dnsRes.forEach(record => { dnsRes.forEach(record => {
dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `; dnsMessage += `Hostname: ${record.exchange} - Priority: ${record.priority} | `;
}); });
dnsMessage = dnsMessage.slice(0, -2) dnsMessage = dnsMessage.slice(0, -2);
} else if (this.dns_resolve_type == "NS") { } else if (this.dns_resolve_type == "NS") {
dnsMessage += "Servers: "; dnsMessage += "Servers: ";
dnsMessage += dnsRes.join(" | "); dnsMessage += dnsRes.join(" | ");
@ -222,7 +222,7 @@ class Monitor extends BeanModel {
dnsRes.forEach(record => { dnsRes.forEach(record => {
dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `; dnsMessage += `Name: ${record.name} | Port: ${record.port} | Priority: ${record.priority} | Weight: ${record.weight} | `;
}); });
dnsMessage = dnsMessage.slice(0, -2) dnsMessage = dnsMessage.slice(0, -2);
} }
if (this.dnsLastResult !== dnsMessage) { if (this.dnsLastResult !== dnsMessage) {
@ -285,20 +285,20 @@ class Monitor extends BeanModel {
if (!isFirstBeat || bean.status === DOWN) { if (!isFirstBeat || bean.status === DOWN) {
let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [ let notificationList = await R.getAll("SELECT notification.* FROM notification, monitor_notification WHERE monitor_id = ? AND monitor_notification.notification_id = notification.id ", [
this.id, this.id,
]) ]);
let text; let text;
if (bean.status === UP) { if (bean.status === UP) {
text = "✅ Up" text = "✅ Up";
} else { } else {
text = "🔴 Down" text = "🔴 Down";
} }
let msg = `[${this.name}] [${text}] ${bean.msg}`; let msg = `[${this.name}] [${text}] ${bean.msg}`;
for (let notification of notificationList) { for (let notification of notificationList) {
try { try {
await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON()) await Notification.send(JSON.parse(notification.config), msg, await this.toJSON(), bean.toJSON());
} catch (e) { } catch (e) {
console.error("Cannot send notification to " + notification.name); console.error("Cannot send notification to " + notification.name);
console.log(e); console.log(e);
@ -313,18 +313,18 @@ class Monitor extends BeanModel {
let beatInterval = this.interval; 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) {
if (this.retryInterval !== this.interval) { if (this.retryInterval !== this.interval) {
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 { } 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}`);
} }
io.to(this.user_id).emit("heartbeat", bean.toJSON()); io.to(this.user_id).emit("heartbeat", bean.toJSON());
Monitor.sendStats(io, this.id, this.user_id) Monitor.sendStats(io, this.id, this.user_id);
await R.store(bean); await R.store(bean);
prometheus.update(bean, tlsInfo); prometheus.update(bean, tlsInfo);
@ -335,7 +335,7 @@ class Monitor extends BeanModel {
this.heartbeatInterval = setTimeout(beat, beatInterval * 1000); this.heartbeatInterval = setTimeout(beat, beatInterval * 1000);
} }
} };
beat(); beat();
} }
@ -481,7 +481,7 @@ class Monitor extends BeanModel {
} else { } else {
// Handle new monitor with only one beat, because the beat's duration = 0 // Handle new monitor with only one beat, because the beat's duration = 0
let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ])); let status = parseInt(await R.getCell("SELECT `status` FROM heartbeat WHERE monitor_id = ?", [ monitorID ]));
console.log("here???" + status);
if (status === UP) { if (status === UP) {
uptime = 1; uptime = 1;
} }

View file

@ -7,9 +7,9 @@
:animation="100" :animation="100"
> >
<template #item="group"> <template #item="group">
<div> <div class="mb-5 ">
<!-- Group Title --> <!-- Group Title -->
<h2 class="mt-5 group-title"> <h2 class="group-title">
<font-awesome-icon v-if="editMode && showGroupDrag" icon="arrows-alt-v" class="action drag me-3" /> <font-awesome-icon v-if="editMode && showGroupDrag" icon="arrows-alt-v" class="action drag me-3" />
<font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeGroup(group.index)" /> <font-awesome-icon v-if="editMode" icon="times" class="action remove me-3" @click="removeGroup(group.index)" />
<Editable v-model="group.element.name" :contenteditable="editMode" tag="span" /> <Editable v-model="group.element.name" :contenteditable="editMode" tag="span" />
@ -76,7 +76,7 @@ export default {
data() { data() {
return { return {
} };
}, },
computed: { computed: {
showGroupDrag() { showGroupDrag() {
@ -95,7 +95,7 @@ export default {
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1); this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
}, },
} }
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -184,7 +184,7 @@
</template> </template>
<script> <script>
import VueMultiselect from "vue-multiselect" import VueMultiselect from "vue-multiselect";
import axios from "axios"; import axios from "axios";
import PublicGroupList from "../components/PublicGroupList.vue"; import PublicGroupList from "../components/PublicGroupList.vue";
import ImageCropUpload from "vue-image-crop-upload"; import ImageCropUpload from "vue-image-crop-upload";
@ -194,6 +194,8 @@ const toast = useToast();
const leavePageMsg = "Do you really want to leave? you have unsaved changes!"; const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
let feedInterval;
export default { export default {
components: { components: {
PublicGroupList, PublicGroupList,
@ -222,9 +224,10 @@ export default {
config: {}, config: {},
selectedMonitor: null, selectedMonitor: null,
incident: null, incident: null,
previousIncident: null,
showImageCropUpload: false, showImageCropUpload: false,
imgDataUrl: "/icon.svg", imgDataUrl: "/icon.svg",
} };
}, },
computed: { computed: {
@ -264,7 +267,7 @@ export default {
if (this.editMode) { if (this.editMode) {
return { return {
"edit-mode": true, "edit-mode": true,
} };
} }
return {}; return {};
}, },
@ -359,6 +362,16 @@ export default {
axios.get("/api/status-page/monitor-list").then((res) => { axios.get("/api/status-page/monitor-list").then((res) => {
this.monitorList = res.data; this.monitorList = res.data;
}); });
// 5mins a loop
feedInterval = setInterval(() => {
// If editMode, it will use the data from websocket.
if (! this.editMode) {
axios.get("/api/status-page/heartbeat").then((res) => {
// TODO
});
}
}, 5 * 60 * 1000);
}, },
methods: { methods: {
@ -385,7 +398,7 @@ export default {
this.$root.publicGroupList.push({ this.$root.publicGroupList.push({
name: groupName, name: groupName,
monitorList: [], monitorList: [],
}) });
}, },
discard() { discard() {
@ -411,6 +424,11 @@ export default {
createIncident() { createIncident() {
this.enableEditIncidentMode = true; this.enableEditIncidentMode = true;
if (this.incident) {
this.previousIncident = this.incident;
}
this.incident = { this.incident = {
title: "", title: "",
content: "", content: "",
@ -442,19 +460,25 @@ export default {
*/ */
editIncident() { editIncident() {
this.enableEditIncidentMode = true; this.enableEditIncidentMode = true;
this.previousIncident = Object.assign({}, this.incident);
}, },
cancelIncident() { cancelIncident() {
this.enableEditIncidentMode = false; this.enableEditIncidentMode = false;
if (this.previousIncident) {
this.incident = this.previousIncident;
this.previousIncident = null;
}
}, },
unpinIncident() { unpinIncident() {
this.$root.getSocket().emit("unpinIncident", () => { this.$root.getSocket().emit("unpinIncident", () => {
this.incident = null; this.incident = null;
}) });
} }
} }
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>