diff --git a/db/patch-add-certificate-expiry-status-page.sql b/db/patch-add-certificate-expiry-status-page.sql new file mode 100644 index 000000000..63a20105b --- /dev/null +++ b/db/patch-add-certificate-expiry-status-page.sql @@ -0,0 +1,7 @@ +-- You should not modify if this have pushed to Github, unless it does serious wrong with the db. +BEGIN TRANSACTION; + +ALTER TABLE status_page + ADD show_certificate_expiry BOOLEAN default 0 NOT NULL; + +COMMIT; diff --git a/server/database.js b/server/database.js index 0f4f78325..7b1d9f932 100644 --- a/server/database.js +++ b/server/database.js @@ -74,6 +74,7 @@ class Database { "patch-add-invert-keyword.sql": true, "patch-added-json-query.sql": true, "patch-added-kafka-producer.sql": true, + "patch-add-certificate-expiry-status-page.sql": true, }; /** diff --git a/server/model/group.js b/server/model/group.js index 3f3b3b129..5b712aceb 100644 --- a/server/model/group.js +++ b/server/model/group.js @@ -9,12 +9,12 @@ class Group extends BeanModel { * @param {boolean} [showTags=false] Should the JSON include monitor tags * @returns {Object} */ - async toPublicJSON(showTags = false) { + async toPublicJSON(showTags = false, certExpiry = false) { let monitorBeanList = await this.getMonitorList(); let monitorList = []; for (let bean of monitorBeanList) { - monitorList.push(await bean.toPublicJSON(showTags)); + monitorList.push(await bean.toPublicJSON(showTags, certExpiry)); } return { diff --git a/server/model/monitor.js b/server/model/monitor.js index 06f6a7d5e..f28b4fe23 100644 --- a/server/model/monitor.js +++ b/server/model/monitor.js @@ -37,11 +37,12 @@ class Monitor extends BeanModel { * Only show necessary data to public * @returns {Object} */ - async toPublicJSON(showTags = false) { + async toPublicJSON(showTags = false, certExpiry = false) { let obj = { id: this.id, name: this.name, sendUrl: this.sendUrl, + type: this.type, }; if (this.sendUrl) { @@ -51,6 +52,13 @@ class Monitor extends BeanModel { if (showTags) { obj.tags = await this.getTags(); } + + if (certExpiry && this.type === "http") { + const { certExpiryDaysRemaining, validCert } = await this.getCertExpiry(this.id); + obj.certExpiryDaysRemaining = certExpiryDaysRemaining; + obj.validCert = validCert; + } + return obj; } @@ -184,6 +192,31 @@ class Monitor extends BeanModel { return await R.getAll("SELECT mt.*, tag.name, tag.color FROM monitor_tag mt JOIN tag ON mt.tag_id = tag.id WHERE mt.monitor_id = ? ORDER BY tag.name", [ this.id ]); } + /** + * Gets certificate expiry for this monitor + * @param {number} monitorID ID of monitor to send + * @returns {Promise>} + */ + async getCertExpiry(monitorID) { + let tlsInfoBean = await R.findOne("monitor_tls_info", "monitor_id = ?", [ + monitorID, + ]); + let tlsInfo; + if (tlsInfoBean) { + tlsInfo = JSON.parse(tlsInfoBean?.info_json); + if (tlsInfo?.valid && tlsInfo?.certInfo?.daysRemaining) { + return { + certExpiryDaysRemaining: tlsInfo.certInfo.daysRemaining, + validCert: true + }; + } + } + return { + certExpiryDaysRemaining: "", + validCert: false + }; + } + /** * Encode user and password to Base64 encoding * for HTTP "basic" auth, as per RFC-7617 diff --git a/server/model/status_page.js b/server/model/status_page.js index 65b77367e..e168acf2f 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -90,6 +90,8 @@ class StatusPage extends BeanModel { * @param {StatusPage} statusPage */ static async getStatusPageData(statusPage) { + const config = await statusPage.toPublicJSON(); + // Incident let incident = await R.findOne("incident", " pin = 1 AND active = 1 AND status_page_id = ? ", [ statusPage.id, @@ -110,13 +112,13 @@ class StatusPage extends BeanModel { ]); for (let groupBean of list) { - let monitorGroup = await groupBean.toPublicJSON(showTags); + let monitorGroup = await groupBean.toPublicJSON(showTags, config?.showCertificateExpiry); publicGroupList.push(monitorGroup); } // Response return { - config: await statusPage.toPublicJSON(), + config, incident, publicGroupList, maintenanceList, @@ -234,6 +236,7 @@ class StatusPage extends BeanModel { footerText: this.footer_text, showPoweredBy: !!this.show_powered_by, googleAnalyticsId: this.google_analytics_tag_id, + showCertificateExpiry: !!this.show_certificate_expiry, }; } @@ -255,6 +258,7 @@ class StatusPage extends BeanModel { footerText: this.footer_text, showPoweredBy: !!this.show_powered_by, googleAnalyticsId: this.google_analytics_tag_id, + showCertificateExpiry: !!this.show_certificate_expiry, }; } diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 411bda556..eba40daec 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -162,6 +162,7 @@ module.exports.statusPageSocketHandler = (socket) => { statusPage.footer_text = config.footerText; statusPage.custom_css = config.customCSS; statusPage.show_powered_by = config.showPoweredBy; + statusPage.show_certificate_expiry = config.showCertificateExpiry; statusPage.modified_date = R.isoDateTime(); statusPage.google_analytics_tag_id = config.googleAnalyticsId; diff --git a/src/components/PublicGroupList.vue b/src/components/PublicGroupList.vue index 4bd4a2952..b885a1a17 100644 --- a/src/components/PublicGroupList.vue +++ b/src/components/PublicGroupList.vue @@ -61,8 +61,13 @@ /> -
- +
+
+ +
+
+ +
@@ -103,6 +108,10 @@ export default { /** Should tags be shown? */ showTags: { type: Boolean, + }, + /** Should expiry be shown? */ + showCertificateExpiry: { + type: Boolean, } }, data() { @@ -154,6 +163,33 @@ export default { } return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode; }, + + /** + * Returns formatted certificate expiry or Bad cert message + * @param {Object} monitor Monitor to show expiry for + * @returns {string} + */ + formattedCertExpiryMessage(monitor) { + if (monitor?.element?.validCert && monitor?.element?.certExpiryDaysRemaining) { + return monitor.element.certExpiryDaysRemaining + " " + this.$tc("day", monitor.element.certExpiryDaysRemaining); + } else if (monitor?.element?.validCert === false) { + return this.$t("noOrBadCertificate"); + } else { + return this.$t("Unknown") + " " + this.$tc("day", 2); + } + }, + + /** + * Returns certificate expiry based on days remaining + * @param {Object} monitor Monitor to show expiry for + * @returns {string} + */ + certExpiryColor(monitor) { + if (monitor?.element?.validCert && monitor.element.certExpiryDaysRemaining > 7) { + return "#059669"; + } + return "#DC2626"; + }, } }; @@ -161,6 +197,15 @@ export default {