mirror of
				https://github.com/louislam/uptime-kuma.git
				synced 2025-10-31 11:29:20 +08:00 
			
		
		
		
	Merge pull request #3357 from tarun7singh/status-page-expiry
Status page certificate expiry
This commit is contained in:
		
							
								
								
									
										7
									
								
								db/patch-add-certificate-expiry-status-page.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								db/patch-add-certificate-expiry-status-page.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -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; | ||||||
| @@ -74,6 +74,7 @@ class Database { | |||||||
|         "patch-add-invert-keyword.sql": true, |         "patch-add-invert-keyword.sql": true, | ||||||
|         "patch-added-json-query.sql": true, |         "patch-added-json-query.sql": true, | ||||||
|         "patch-added-kafka-producer.sql": true, |         "patch-added-kafka-producer.sql": true, | ||||||
|  |         "patch-add-certificate-expiry-status-page.sql": true, | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -9,12 +9,12 @@ class Group extends BeanModel { | |||||||
|      * @param {boolean} [showTags=false] Should the JSON include monitor tags |      * @param {boolean} [showTags=false] Should the JSON include monitor tags | ||||||
|      * @returns {Object} |      * @returns {Object} | ||||||
|      */ |      */ | ||||||
|     async toPublicJSON(showTags = false) { |     async toPublicJSON(showTags = false, certExpiry = false) { | ||||||
|         let monitorBeanList = await this.getMonitorList(); |         let monitorBeanList = await this.getMonitorList(); | ||||||
|         let monitorList = []; |         let monitorList = []; | ||||||
|  |  | ||||||
|         for (let bean of monitorBeanList) { |         for (let bean of monitorBeanList) { | ||||||
|             monitorList.push(await bean.toPublicJSON(showTags)); |             monitorList.push(await bean.toPublicJSON(showTags, certExpiry)); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return { |         return { | ||||||
|   | |||||||
| @@ -37,11 +37,12 @@ class Monitor extends BeanModel { | |||||||
|      * Only show necessary data to public |      * Only show necessary data to public | ||||||
|      * @returns {Object} |      * @returns {Object} | ||||||
|      */ |      */ | ||||||
|     async toPublicJSON(showTags = false) { |     async toPublicJSON(showTags = false, certExpiry = false) { | ||||||
|         let obj = { |         let obj = { | ||||||
|             id: this.id, |             id: this.id, | ||||||
|             name: this.name, |             name: this.name, | ||||||
|             sendUrl: this.sendUrl, |             sendUrl: this.sendUrl, | ||||||
|  |             type: this.type, | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         if (this.sendUrl) { |         if (this.sendUrl) { | ||||||
| @@ -51,6 +52,13 @@ class Monitor extends BeanModel { | |||||||
|         if (showTags) { |         if (showTags) { | ||||||
|             obj.tags = await this.getTags(); |             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; |         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 ]); |         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<LooseObject<any>>} | ||||||
|  |      */ | ||||||
|  |     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 |      * Encode user and password to Base64 encoding | ||||||
|      * for HTTP "basic" auth, as per RFC-7617 |      * for HTTP "basic" auth, as per RFC-7617 | ||||||
|   | |||||||
| @@ -90,6 +90,8 @@ class StatusPage extends BeanModel { | |||||||
|      * @param {StatusPage} statusPage |      * @param {StatusPage} statusPage | ||||||
|      */ |      */ | ||||||
|     static async getStatusPageData(statusPage) { |     static async getStatusPageData(statusPage) { | ||||||
|  |         const config = await statusPage.toPublicJSON(); | ||||||
|  |  | ||||||
|         // Incident |         // 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 AND status_page_id = ? ", [ | ||||||
|             statusPage.id, |             statusPage.id, | ||||||
| @@ -110,13 +112,13 @@ class StatusPage extends BeanModel { | |||||||
|         ]); |         ]); | ||||||
|  |  | ||||||
|         for (let groupBean of list) { |         for (let groupBean of list) { | ||||||
|             let monitorGroup = await groupBean.toPublicJSON(showTags); |             let monitorGroup = await groupBean.toPublicJSON(showTags, config?.showCertificateExpiry); | ||||||
|             publicGroupList.push(monitorGroup); |             publicGroupList.push(monitorGroup); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // Response |         // Response | ||||||
|         return { |         return { | ||||||
|             config: await statusPage.toPublicJSON(), |             config, | ||||||
|             incident, |             incident, | ||||||
|             publicGroupList, |             publicGroupList, | ||||||
|             maintenanceList, |             maintenanceList, | ||||||
| @@ -234,6 +236,7 @@ class StatusPage extends BeanModel { | |||||||
|             footerText: this.footer_text, |             footerText: this.footer_text, | ||||||
|             showPoweredBy: !!this.show_powered_by, |             showPoweredBy: !!this.show_powered_by, | ||||||
|             googleAnalyticsId: this.google_analytics_tag_id, |             googleAnalyticsId: this.google_analytics_tag_id, | ||||||
|  |             showCertificateExpiry: !!this.show_certificate_expiry, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -255,6 +258,7 @@ class StatusPage extends BeanModel { | |||||||
|             footerText: this.footer_text, |             footerText: this.footer_text, | ||||||
|             showPoweredBy: !!this.show_powered_by, |             showPoweredBy: !!this.show_powered_by, | ||||||
|             googleAnalyticsId: this.google_analytics_tag_id, |             googleAnalyticsId: this.google_analytics_tag_id, | ||||||
|  |             showCertificateExpiry: !!this.show_certificate_expiry, | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -162,6 +162,7 @@ module.exports.statusPageSocketHandler = (socket) => { | |||||||
|             statusPage.footer_text = config.footerText; |             statusPage.footer_text = config.footerText; | ||||||
|             statusPage.custom_css = config.customCSS; |             statusPage.custom_css = config.customCSS; | ||||||
|             statusPage.show_powered_by = config.showPoweredBy; |             statusPage.show_powered_by = config.showPoweredBy; | ||||||
|  |             statusPage.show_certificate_expiry = config.showCertificateExpiry; | ||||||
|             statusPage.modified_date = R.isoDateTime(); |             statusPage.modified_date = R.isoDateTime(); | ||||||
|             statusPage.google_analytics_tag_id = config.googleAnalyticsId; |             statusPage.google_analytics_tag_id = config.googleAnalyticsId; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -61,8 +61,13 @@ | |||||||
|                                                 /> |                                                 /> | ||||||
|                                             </span> |                                             </span> | ||||||
|                                         </div> |                                         </div> | ||||||
|                                         <div v-if="showTags" class="tags"> |                                         <div class="extra-info"> | ||||||
|                                             <Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" /> |                                             <div v-if="showCertificateExpiry && monitor.element.type === 'http'"> | ||||||
|  |                                                 <Tag :item="{name: $t('Cert Exp.'), value: formattedCertExpiryMessage(monitor), color: certExpiryColor(monitor)}" :size="'sm'" /> | ||||||
|  |                                             </div> | ||||||
|  |                                             <div v-if="showTags"> | ||||||
|  |                                                 <Tag v-for="tag in monitor.element.tags" :key="tag" :item="tag" :size="'sm'" /> | ||||||
|  |                                             </div> | ||||||
|                                         </div> |                                         </div> | ||||||
|                                     </div> |                                     </div> | ||||||
|                                     <div :key="$root.userHeartbeatBar" class="col-3 col-md-4"> |                                     <div :key="$root.userHeartbeatBar" class="col-3 col-md-4"> | ||||||
| @@ -103,6 +108,10 @@ export default { | |||||||
|         /** Should tags be shown? */ |         /** Should tags be shown? */ | ||||||
|         showTags: { |         showTags: { | ||||||
|             type: Boolean, |             type: Boolean, | ||||||
|  |         }, | ||||||
|  |         /** Should expiry be shown? */ | ||||||
|  |         showCertificateExpiry: { | ||||||
|  |             type: Boolean, | ||||||
|         } |         } | ||||||
|     }, |     }, | ||||||
|     data() { |     data() { | ||||||
| @@ -154,6 +163,33 @@ export default { | |||||||
|             } |             } | ||||||
|             return monitor.element.sendUrl && monitor.element.url && monitor.element.url !== "https://" && !this.editMode; |             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"; | ||||||
|  |         }, | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
| </script> | </script> | ||||||
| @@ -161,6 +197,15 @@ export default { | |||||||
| <style lang="scss" scoped> | <style lang="scss" scoped> | ||||||
| @import "../assets/vars"; | @import "../assets/vars"; | ||||||
|  |  | ||||||
|  | .extra-info { | ||||||
|  |     display: flex; | ||||||
|  |     margin-bottom: 0.5rem; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .extra-info > div > div:first-child { | ||||||
|  |     margin-left: 0 !important; | ||||||
|  | } | ||||||
|  |  | ||||||
| .no-monitor-msg { | .no-monitor-msg { | ||||||
|     position: absolute; |     position: absolute; | ||||||
|     width: 100%; |     width: 100%; | ||||||
|   | |||||||
| @@ -793,5 +793,7 @@ | |||||||
|     "nostrRelaysHelp": "One relay URL per line", |     "nostrRelaysHelp": "One relay URL per line", | ||||||
|     "nostrSender": "Sender Private Key (nsec)", |     "nostrSender": "Sender Private Key (nsec)", | ||||||
|     "nostrRecipients": "Recipients Public Keys (npub)", |     "nostrRecipients": "Recipients Public Keys (npub)", | ||||||
|     "nostrRecipientsHelp": "npub format, one per line" |     "nostrRecipientsHelp": "npub format, one per line", | ||||||
|  |     "showCertificateExpiry": "Show Certificate Expiry", | ||||||
|  |     "noOrBadCertificate": "No/Bad Certificate" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -39,5 +39,6 @@ | |||||||
|     "Reconnecting...": "पुनः कनेक्ट किया जा रहा है...", |     "Reconnecting...": "पुनः कनेक्ट किया जा रहा है...", | ||||||
|     "Down": "बंद", |     "Down": "बंद", | ||||||
|     "Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार", |     "Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार", | ||||||
|     "Status": "स्थिति" |     "Status": "स्थिति", | ||||||
|  |     "showCertificateExpiry": "प्रमाणपत्र समाप्ति दिखाएँ" | ||||||
| } | } | ||||||
|   | |||||||
| @@ -54,6 +54,12 @@ | |||||||
|                     <label class="form-check-label" for="show-powered-by">{{ $t("Show Powered By") }}</label> |                     <label class="form-check-label" for="show-powered-by">{{ $t("Show Powered By") }}</label> | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|  |                 <!-- Show certificate expiry --> | ||||||
|  |                 <div class="my-3 form-check form-switch"> | ||||||
|  |                     <input id="show-certificate-expiry" v-model="config.showCertificateExpiry" class="form-check-input" type="checkbox"> | ||||||
|  |                     <label class="form-check-label" for="show-certificate-expiry">{{ $t("showCertificateExpiry") }}</label> | ||||||
|  |                 </div> | ||||||
|  |  | ||||||
|                 <div v-if="false" class="my-3"> |                 <div v-if="false" class="my-3"> | ||||||
|                     <label for="password" class="form-label">{{ $t("Password") }} <sup>{{ $t("Coming Soon") }}</sup></label> |                     <label for="password" class="form-label">{{ $t("Password") }} <sup>{{ $t("Coming Soon") }}</sup></label> | ||||||
|                     <input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control"> |                     <input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control"> | ||||||
| @@ -309,7 +315,7 @@ | |||||||
|                     👀 {{ $t("statusPageNothing") }} |                     👀 {{ $t("statusPageNothing") }} | ||||||
|                 </div> |                 </div> | ||||||
|  |  | ||||||
|                 <PublicGroupList :edit-mode="enableEditMode" :show-tags="config.showTags" /> |                 <PublicGroupList :edit-mode="enableEditMode" :show-tags="config.showTags" :show-certificate-expiry="config.showCertificateExpiry" /> | ||||||
|             </div> |             </div> | ||||||
|  |  | ||||||
|             <footer class="mt-5 mb-4"> |             <footer class="mt-5 mb-4"> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user