diff --git a/server/model/status_page.js b/server/model/status_page.js index ebab30165..4413e6650 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -23,7 +23,7 @@ class StatusPage extends BeanModel { ]); if (statusPage) { - response.send(StatusPage.renderHTML(indexHTML, statusPage)); + response.send(await StatusPage.renderHTML(indexHTML, statusPage)); } else { response.status(404).send(UptimeKumaServer.getInstance().indexHTML); } @@ -34,7 +34,7 @@ class StatusPage extends BeanModel { * @param {string} indexHTML * @param {StatusPage} statusPage */ - static renderHTML(indexHTML, statusPage) { + static async renderHTML(indexHTML, statusPage) { const $ = cheerio.load(indexHTML); const description155 = statusPage.description.substring(0, 155); @@ -53,9 +53,52 @@ class StatusPage extends BeanModel { head.append(``); head.append(``); + // Preload data + const json = JSON.stringify(await StatusPage.getStatusPageData(statusPage)); + head.append(` + + `); + return $.root().html(); } + /** + * Get all status page data in one call + * @param {StatusPage} statusPage + */ + static async getStatusPageData(statusPage) { + // Incident + let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [ + statusPage.id, + ]); + + if (incident) { + incident = incident.toPublicJSON(); + } + + // Public Group List + const publicGroupList = []; + const showTags = !!statusPage.show_tags; + + const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ + statusPage.id + ]); + + for (let groupBean of list) { + let monitorGroup = await groupBean.toPublicJSON(showTags); + publicGroupList.push(monitorGroup); + } + + // Response + return { + config: await statusPage.toPublicJSON(), + incident, + publicGroupList + }; + } + /** * Loads domain mapping from DB * Return object like this: { "test-uptime.kuma.pet": "default" } diff --git a/server/routers/api-router.js b/server/routers/api-router.js index 24a42069c..f4628bc61 100644 --- a/server/routers/api-router.js +++ b/server/routers/api-router.js @@ -1,5 +1,5 @@ let express = require("express"); -const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin } = require("../util-server"); +const { allowDevAllOrigin, allowAllOrigin, percentageToColor, filterAndJoin, send403 } = require("../util-server"); const { R } = require("redbean-node"); const apicache = require("../modules/apicache"); const Monitor = require("../model/monitor"); @@ -99,108 +99,6 @@ router.get("/api/push/:pushToken", async (request, response) => { } }); -// Status page config, incident, monitor list -router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => { - allowDevAllOrigin(response); - let slug = request.params.slug; - - // Get Status Page - let statusPage = await R.findOne("status_page", " slug = ? ", [ - slug - ]); - - if (!statusPage) { - response.statusCode = 404; - response.json({ - msg: "Not Found" - }); - return; - } - - try { - // Incident - let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [ - statusPage.id, - ]); - - if (incident) { - incident = incident.toPublicJSON(); - } - - // Public Group List - const publicGroupList = []; - const showTags = !!statusPage.show_tags; - - const list = await R.find("group", " public = 1 AND status_page_id = ? ORDER BY weight ", [ - statusPage.id - ]); - - for (let groupBean of list) { - let monitorGroup = await groupBean.toPublicJSON(showTags); - publicGroupList.push(monitorGroup); - } - - // Response - response.json({ - config: await statusPage.toPublicJSON(), - incident, - publicGroupList - }); - - } catch (error) { - send403(response, error.message); - } - -}); - -// Status Page Polling Data -// Can fetch only if published -router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => { - allowDevAllOrigin(response); - - try { - let heartbeatList = {}; - let uptimeList = {}; - - let slug = request.params.slug; - let statusPageID = await StatusPage.slugToID(slug); - - let monitorIDList = await R.getCol(` - SELECT monitor_group.monitor_id FROM monitor_group, \`group\` - WHERE monitor_group.group_id = \`group\`.id - AND public = 1 - AND \`group\`.status_page_id = ? - `, [ - statusPageID - ]); - - for (let monitorID of monitorIDList) { - let list = await R.getAll(` - SELECT * FROM heartbeat - WHERE monitor_id = ? - ORDER BY time DESC - LIMIT 50 - `, [ - monitorID, - ]); - - list = R.convertToBeans("heartbeat", list); - heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON()); - - const type = 24; - uptimeList[`${monitorID}_${type}`] = await Monitor.calcUptime(type, monitorID); - } - - response.json({ - heartbeatList, - uptimeList - }); - - } catch (error) { - send403(response, error.message); - } -}); - router.get("/api/badge/:id/status", cache("5 minutes"), async (request, response) => { allowAllOrigin(response); @@ -377,16 +275,4 @@ router.get("/api/badge/:id/ping/:duration?", cache("5 minutes"), async (request, } }); -/** - * Send a 403 response - * @param {Object} res Express response object - * @param {string} [msg=""] Message to send - */ -function send403(res, msg = "") { - res.status(403).json({ - "status": "fail", - "msg": msg, - }); -} - module.exports = router; diff --git a/server/routers/status-page-router.js b/server/routers/status-page-router.js index 77b791136..465afdf88 100644 --- a/server/routers/status-page-router.js +++ b/server/routers/status-page-router.js @@ -2,6 +2,9 @@ let express = require("express"); const apicache = require("../modules/apicache"); const { UptimeKumaServer } = require("../uptime-kuma-server"); const StatusPage = require("../model/status_page"); +const { allowDevAllOrigin, send403 } = require("../util-server"); +const { R } = require("redbean-node"); +const Monitor = require("../model/monitor"); let router = express.Router(); @@ -23,4 +26,85 @@ router.get("/status-page", cache("5 minutes"), async (request, response) => { await StatusPage.handleStatusPageResponse(response, server.indexHTML, slug); }); +// Status page config, incident, monitor list +router.get("/api/status-page/:slug", cache("5 minutes"), async (request, response) => { + allowDevAllOrigin(response); + let slug = request.params.slug; + + try { + // Get Status Page + let statusPage = await R.findOne("status_page", " slug = ? ", [ + slug + ]); + + if (!statusPage) { + return null; + } + + let statusPageData = await StatusPage.getStatusPageData(statusPage); + + if (!statusPageData) { + response.statusCode = 404; + response.json({ + msg: "Not Found" + }); + return; + } + + // Response + response.json(statusPageData); + + } catch (error) { + send403(response, error.message); + } +}); + +// Status Page Polling Data +// Can fetch only if published +router.get("/api/status-page/heartbeat/:slug", cache("1 minutes"), async (request, response) => { + allowDevAllOrigin(response); + + try { + let heartbeatList = {}; + let uptimeList = {}; + + let slug = request.params.slug; + let statusPageID = await StatusPage.slugToID(slug); + + let monitorIDList = await R.getCol(` + SELECT monitor_group.monitor_id FROM monitor_group, \`group\` + WHERE monitor_group.group_id = \`group\`.id + AND public = 1 + AND \`group\`.status_page_id = ? + `, [ + statusPageID + ]); + + for (let monitorID of monitorIDList) { + let list = await R.getAll(` + SELECT * FROM heartbeat + WHERE monitor_id = ? + ORDER BY time DESC + LIMIT 50 + `, [ + monitorID, + ]); + + list = R.convertToBeans("heartbeat", list); + heartbeatList[monitorID] = list.reverse().map(row => row.toPublicJSON()); + + const type = 24; + uptimeList[`${monitorID}_${type}`] = await Monitor.calcUptime(type, monitorID); + } + + response.json({ + heartbeatList, + uptimeList + }); + + } catch (error) { + send403(response, error.message); + } +}); + module.exports = router; diff --git a/server/util-server.js b/server/util-server.js index db7f525c7..71b2c505b 100644 --- a/server/util-server.js +++ b/server/util-server.js @@ -185,7 +185,7 @@ exports.dnsResolve = function (hostname, resolverServer, resolverPort, rrtype) { // Remove brackets from IPv6 addresses so we can re-add them to // prevent issues with ::1:5300 (::1 port 5300) resolverServer = resolverServer.replace("[", "").replace("]", ""); - resolver.setServers([`[${resolverServer}]:${resolverPort}`]); + resolver.setServers([ `[${resolverServer}]:${resolverPort}` ]); return new Promise((resolve, reject) => { if (rrtype === "PTR") { resolver.reverse(hostname, (err, records) => { @@ -558,3 +558,15 @@ exports.percentageToColor = (percentage, maxHue = 90, minHue = 10) => { exports.filterAndJoin = (parts, connector = "") => { return parts.filter((part) => !!part && part !== "").join(connector); }; + +/** + * Send a 403 response + * @param {Object} res Express response object + * @param {string} [msg=""] Message to send + */ +module.exports.send403 = (res, msg = "") => { + res.status(403).json({ + "status": "fail", + "msg": msg, + }); +}; diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index c75022531..fbcbf9e8e 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -538,7 +538,7 @@ export default { this.slug = "default"; } - axios.get("/api/status-page/" + this.slug).then((res) => { + this.getData().then((res) => { this.config = res.data.config; if (!this.config.domainNameList) { @@ -567,6 +567,21 @@ export default { }, methods: { + /** + * Get status page data + * It should be preloaded in window.preloadData + * @returns {Promise} + */ + getData: function () { + if (window.preloadData) { + return new Promise(resolve => resolve({ + data: window.preloadData + })); + } else { + return axios.get("/api/status-page/" + this.slug); + } + }, + highlighter(code) { return highlight(code, languages.css); },