1
0
mirror of https://github.com/louislam/uptime-kuma.git synced 2024-11-24 08:32:23 +02:00

Merge pull request #3357 from tarun7singh/status-page-expiry

Status page certificate expiry
This commit is contained in:
Louis Lam 2023-07-31 17:46:13 +08:00 committed by GitHub
commit 27ce47277b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 110 additions and 10 deletions

View 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;

View File

@ -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,
}; };
/** /**

View File

@ -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 {

View File

@ -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

View File

@ -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,
}; };
} }

View File

@ -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;

View File

@ -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%;

View File

@ -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"
} }

View File

@ -39,5 +39,6 @@
"Reconnecting...": "पुनः कनेक्ट किया जा रहा है...", "Reconnecting...": "पुनः कनेक्ट किया जा रहा है...",
"Down": "बंद", "Down": "बंद",
"Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार", "Passive Monitor Type": "निष्क्रिय मॉनिटर प्रकार",
"Status": "स्थिति" "Status": "स्थिति",
"showCertificateExpiry": "प्रमाणपत्र समाप्ति दिखाएँ"
} }

View File

@ -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">