[Status Page] wip, combine api, add status_page_id into group and incident tables

This commit is contained in:
Louis Lam 2022-03-16 15:38:10 +08:00
parent 18ec42b060
commit 1033ca5cf4
6 changed files with 105 additions and 90 deletions

View file

@ -25,4 +25,7 @@ CREATE TABLE [status_page_cname](
[domain] VARCHAR NOT NULL UNIQUE [domain] VARCHAR NOT NULL UNIQUE
); );
ALTER TABLE incident ADD status_page_id INTEGER;
ALTER TABLE [group] ADD status_page_id INTEGER;
COMMIT; COMMIT;

View file

@ -240,8 +240,18 @@ class Database {
statusPage.search_engine_index = await setting("searchEngineIndex"); statusPage.search_engine_index = await setting("searchEngineIndex");
statusPage.show_tags = await setting("statusPageTags"); statusPage.show_tags = await setting("statusPageTags");
statusPage.password = null; statusPage.password = null;
await R.store(statusPage); let id = await R.store(statusPage);
await R.exec("UPDATE incident SET status_page_id = ? WHERE status_page_id IS NULL", [
id
]);
await R.exec("UPDATE [group] SET status_page_id = ? WHERE status_page_id IS NULL", [
id
]);
await R.exec("DELETE FROM setting WHERE type = 'statusPage'"); await R.exec("DELETE FROM setting WHERE type = 'statusPage'");
console.log("Migrating Status Page - Done"); console.log("Migrating Status Page - Done");
} }

View file

@ -41,6 +41,12 @@ class StatusPage extends BeanModel {
}; };
} }
static async slugToID(slug) {
return await R.getCell("SELECT id FROM status_page WHERE slug = ? ", [
slug
]);
}
} }
module.exports = StatusPage; module.exports = StatusPage;

View file

@ -6,6 +6,7 @@ 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 } = require("../../src/util");
const StatusPage = require("../model/status_page");
let router = express.Router(); let router = express.Router();
let cache = apicache.middleware; let cache = apicache.middleware;
@ -82,11 +83,12 @@ router.get("/api/push/:pushToken", async (request, response) => {
} }
}); });
// Status Page Config // Status page config, incident, monitor list
router.get("/api/status-page/config/:slug", async (request, response) => { router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => {
allowDevAllOrigin(response); allowDevAllOrigin(response);
let slug = request.params.slug; let slug = request.params.slug;
// Get Status Page
let statusPage = await R.findOne("status_page", " slug = ? ", [ let statusPage = await R.findOne("status_page", " slug = ? ", [
slug slug
]); ]);
@ -99,50 +101,30 @@ router.get("/api/status-page/config/:slug", async (request, response) => {
return; return;
} }
response.json(await statusPage.toPublicJSON());
});
// Status Page - Get the current Incident
// Can fetch only if published
router.get("/api/status-page/incident/:slug", async (_, response) => {
allowDevAllOrigin(response);
try { try {
await checkPublished(); // Incident
let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [
let incident = await R.findOne("incident", " pin = 1 AND active = 1"); statusPage.id,
]);
if (incident) { if (incident) {
incident = incident.toPublicJSON(); incident = incident.toPublicJSON();
} }
response.json({ // Public Group List
ok: true,
incident,
});
} catch (error) {
send403(response, error.message);
}
});
// Status Page - Monitor List
// Can fetch only if published
router.get("/api/status-page/monitor-list/:slug", cache("5 minutes"), async (_request, response) => {
allowDevAllOrigin(response);
try {
await checkPublished();
const publicGroupList = []; const publicGroupList = [];
const tagsVisible = (await getSettings("statusPage")).statusPageTags; const tagsVisible = !!statusPage.show_tags;
const list = await R.find("group", " public = 1 ORDER BY weight "); const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [
statusPage.id
]);
for (let groupBean of list) { for (let groupBean of list) {
let monitorGroup = await groupBean.toPublicJSON(); let monitorGroup = await groupBean.toPublicJSON();
if (tagsVisible) { if (tagsVisible) {
monitorGroup.monitorList = await Promise.all(monitorGroup.monitorList.map(async (monitor) => { monitorGroup.monitorList = await Promise.all(monitorGroup.monitorList.map(async (monitor) => {
// Includes tags as an array in response, allows for tags to be displayed on public status page // Includes tags as an array in response, allows for tags to be displayed on public status page
const tags = await R.getAll( const tags = await R.getAll(
`SELECT monitor_tag.monitor_id, monitor_tag.value, tag.name, tag.color `SELECT monitor_tag.monitor_id, monitor_tag.value, tag.name, tag.color
FROM monitor_tag FROM monitor_tag
JOIN tag JOIN tag
ON monitor_tag.tag_id = tag.id ON monitor_tag.tag_id = tag.id
@ -158,29 +140,39 @@ router.get("/api/status-page/monitor-list/:slug", cache("5 minutes"), async (_re
publicGroupList.push(monitorGroup); publicGroupList.push(monitorGroup);
} }
response.json(publicGroupList); // Response
response.json({
config: await statusPage.toPublicJSON(),
incident,
publicGroupList
});
} catch (error) { } catch (error) {
send403(response, error.message); send403(response, error.message);
} }
}); });
// Status Page Polling Data // Status Page Polling Data
// Can fetch only if published // Can fetch only if published
router.get("/api/status-page/heartbeat/:slug", cache("5 minutes"), async (_request, response) => { router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => {
allowDevAllOrigin(response); allowDevAllOrigin(response);
try { try {
await checkPublished();
let heartbeatList = {}; let heartbeatList = {};
let uptimeList = {}; let uptimeList = {};
let slug = request.params.slug;
let statusPageID = await StatusPage.slugToID(slug);
let monitorIDList = await R.getCol(` let monitorIDList = await R.getCol(`
SELECT monitor_group.monitor_id FROM monitor_group, \`group\` SELECT monitor_group.monitor_id FROM monitor_group, \`group\`
WHERE monitor_group.group_id = \`group\`.id WHERE monitor_group.group_id = \`group\`.id
AND public = 1 AND public = 1
`); AND \`group\`.status_page_id = ?
`, [
statusPageID
]);
for (let monitorID of monitorIDList) { for (let monitorID of monitorIDList) {
let list = await R.getAll(` let list = await R.getAll(`
@ -209,12 +201,6 @@ router.get("/api/status-page/heartbeat/:slug", cache("5 minutes"), async (_reque
} }
}); });
async function checkPublished() {
if (! await isPublished()) {
throw new Error("The status page is not published");
}
}
/** /**
* Default is published * Default is published
* @returns {Promise<boolean>} * @returns {Promise<boolean>}

View file

@ -5,21 +5,31 @@ const { debug } = require("../../src/util");
const ImageDataURI = require("../image-data-uri"); const ImageDataURI = require("../image-data-uri");
const Database = require("../database"); const Database = require("../database");
const apicache = require("../modules/apicache"); const apicache = require("../modules/apicache");
const StatusPage = require("../model/status_page");
module.exports.statusPageSocketHandler = (socket) => { module.exports.statusPageSocketHandler = (socket) => {
// Post or edit incident // Post or edit incident
socket.on("postIncident", async (incident, callback) => { socket.on("postIncident", async (slug, incident, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
await R.exec("UPDATE incident SET pin = 0 "); let statusPageID = await StatusPage.slugToID(slug);
if (!statusPageID) {
throw new Error("slug is not found");
}
await R.exec("UPDATE incident SET pin = 0 WHERE status_page_id = ? ", [
statusPageID
]);
let incidentBean; let incidentBean;
if (incident.id) { if (incident.id) {
incidentBean = await R.findOne("incident", " id = ?", [ incidentBean = await R.findOne("incident", " id = ? AND status_page_id = ? ", [
incident.id incident.id,
statusPageID
]); ]);
} }
@ -31,6 +41,7 @@ module.exports.statusPageSocketHandler = (socket) => {
incidentBean.content = incident.content; incidentBean.content = incident.content;
incidentBean.style = incident.style; incidentBean.style = incident.style;
incidentBean.pin = true; incidentBean.pin = true;
incidentBean.status_page_id = statusPageID;
if (incident.id) { if (incident.id) {
incidentBean.lastUpdatedDate = R.isoDateTime(dayjs.utc()); incidentBean.lastUpdatedDate = R.isoDateTime(dayjs.utc());
@ -52,11 +63,15 @@ module.exports.statusPageSocketHandler = (socket) => {
} }
}); });
socket.on("unpinIncident", async (callback) => { socket.on("unpinIncident", async (slug, callback) => {
try { try {
checkLogin(socket); checkLogin(socket);
await R.exec("UPDATE incident SET pin = 0 WHERE pin = 1"); let statusPageID = await StatusPage.slugToID(slug);
await R.exec("UPDATE incident SET pin = 0 WHERE pin = 1 AND status_page_id = ? ", [
statusPageID
]);
callback({ callback({
ok: true, ok: true,
@ -125,13 +140,15 @@ module.exports.statusPageSocketHandler = (socket) => {
for (let group of publicGroupList) { for (let group of publicGroupList) {
let groupBean; let groupBean;
if (group.id) { if (group.id) {
groupBean = await R.findOne("group", " id = ? AND public = 1 ", [ groupBean = await R.findOne("group", " id = ? AND public = 1 AND status_page_id = ? ", [
group.id group.id,
statusPage.id
]); ]);
} else { } else {
groupBean = R.dispense("group"); groupBean = R.dispense("group");
} }
groupBean.status_page_id = statusPage.id;
groupBean.name = group.name; groupBean.name = group.name;
groupBean.public = true; groupBean.public = true;
groupBean.weight = groupOrder++; groupBean.weight = groupOrder++;
@ -143,7 +160,6 @@ module.exports.statusPageSocketHandler = (socket) => {
]); ]);
let monitorOrder = 1; let monitorOrder = 1;
console.log(group.monitorList);
for (let monitor of group.monitorList) { for (let monitor of group.monitorList) {
let relationBean = R.dispense("monitor_group"); let relationBean = R.dispense("monitor_group");

View file

@ -404,9 +404,12 @@ export default {
}, },
"config.showTags"(value) { "config.showTags"(value) {
console.log("here???");
this.changeTagsVisibility(value); this.changeTagsVisibility(value);
} },
"$root.monitorList"() {
this.changeTagsVisibility(this.config.showTags);
},
}, },
async created() { async created() {
@ -437,29 +440,14 @@ export default {
} }
axios.get("/api/status-page/" + this.slug).then((res) => { axios.get("/api/status-page/" + this.slug).then((res) => {
this.config = res.data; this.config = res.data.config;
if (this.config.logo) { if (this.config.logo) {
this.imgDataUrl = this.config.logo; this.imgDataUrl = this.config.logo;
} }
});
axios.get("/api/status-page/config/" + this.slug).then((res) => { this.incident = res.data.incident;
this.config = res.data; this.$root.publicGroupList = res.data.publicGroupList;
if (this.config.logo) {
this.imgDataUrl = this.config.logo;
}
});
axios.get("/api/status-page/incident/" + this.slug).then((res) => {
if (res.data.ok) {
this.incident = res.data.incident;
}
});
axios.get("/api/status-page/monitor-list/" + this.slug).then((res) => {
this.$root.publicGroupList = res.data;
}); });
// 5mins a loop // 5mins a loop
@ -560,21 +548,27 @@ export default {
changeTagsVisibility(show) { changeTagsVisibility(show) {
// On load, the status page will not include tags if it's not enabled for security reasons // If Edit Mode
// Which means if we enable tags, it won't show in the UI until saved if (Object.keys(this.$root.monitorList).length > 0) {
// So we have this to enhance UX and load in the tags from the authenticated source instantly // On load, the status page will not include tags if it's not enabled for security reasons
this.$root.publicGroupList = this.$root.publicGroupList.map((group) => { // Which means if we enable tags, it won't show in the UI until saved
return { // So we have this to enhance UX and load in the tags from the authenticated source instantly
...group, this.$root.publicGroupList = this.$root.publicGroupList.map((group) => {
monitorList: group.monitorList.map((monitor) => { return {
// We only include the tags if visible so we can reuse the logic to hide the tags on disable ...group,
return { monitorList: group.monitorList.map((monitor) => {
...monitor, // We only include the tags if visible so we can reuse the logic to hide the tags on disable
tags: show ? this.$root.monitorList[monitor.id].tags : [] return {
}; ...monitor,
}) tags: show ? this.$root.monitorList[monitor.id].tags : []
}; };
}); })
};
});
} else {
}
}, },
/** /**
@ -610,7 +604,7 @@ export default {
return; return;
} }
this.$root.getSocket().emit("postIncident", this.incident, (res) => { this.$root.getSocket().emit("postIncident", this.slug, this.incident, (res) => {
if (res.ok) { if (res.ok) {
this.enableEditIncidentMode = false; this.enableEditIncidentMode = false;