Merge pull request #2570 from Computroniks/feature/#2365-allow-markdown-in-status-page-footer

Add support for markdown on status page
This commit is contained in:
Louis Lam 2023-02-03 12:36:50 +08:00 committed by GitHub
commit b0673ba9ce
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 3 deletions

32
package-lock.json generated
View file

@ -89,10 +89,12 @@
"cypress": "^10.1.0", "cypress": "^10.1.0",
"delay": "^5.0.0", "delay": "^5.0.0",
"dns2": "~2.0.1", "dns2": "~2.0.1",
"dompurify": "~2.4.3",
"eslint": "~8.14.0", "eslint": "~8.14.0",
"eslint-plugin-vue": "~8.7.1", "eslint-plugin-vue": "~8.7.1",
"favico.js": "~0.3.10", "favico.js": "~0.3.10",
"jest": "~27.2.5", "jest": "~27.2.5",
"marked": "~4.2.5",
"postcss-html": "~1.5.0", "postcss-html": "~1.5.0",
"postcss-rtlcss": "~3.7.2", "postcss-rtlcss": "~3.7.2",
"postcss-scss": "~4.0.4", "postcss-scss": "~4.0.4",
@ -7801,6 +7803,12 @@
"url": "https://github.com/fb55/domhandler?sponsor=1" "url": "https://github.com/fb55/domhandler?sponsor=1"
} }
}, },
"node_modules/dompurify": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==",
"dev": true
},
"node_modules/domutils": { "node_modules/domutils": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
@ -13622,6 +13630,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/marked": {
"version": "4.2.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
"integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/mathml-tag-names": { "node_modules/mathml-tag-names": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",
@ -24880,6 +24900,12 @@
"domelementtype": "^2.3.0" "domelementtype": "^2.3.0"
} }
}, },
"dompurify": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.4.3.tgz",
"integrity": "sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==",
"dev": true
},
"domutils": { "domutils": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz", "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
@ -29114,6 +29140,12 @@
"integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
"dev": true "dev": true
}, },
"marked": {
"version": "4.2.12",
"resolved": "https://registry.npmjs.org/marked/-/marked-4.2.12.tgz",
"integrity": "sha512-yr8hSKa3Fv4D3jdZmtMMPghgVt6TWbk86WQaWhDloQjRSQhMMYCAro7jP7VDJrjjdV8pxVxMssXS8B8Y5DZ5aw==",
"dev": true
},
"mathml-tag-names": { "mathml-tag-names": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz",

View file

@ -146,9 +146,11 @@
"cypress": "^10.1.0", "cypress": "^10.1.0",
"delay": "^5.0.0", "delay": "^5.0.0",
"dns2": "~2.0.1", "dns2": "~2.0.1",
"dompurify": "~2.4.3",
"eslint": "~8.14.0", "eslint": "~8.14.0",
"eslint-plugin-vue": "~8.7.1", "eslint-plugin-vue": "~8.7.1",
"favico.js": "~0.3.10", "favico.js": "~0.3.10",
"marked": "~4.2.5",
"jest": "~27.2.5", "jest": "~27.2.5",
"postcss-html": "~1.5.0", "postcss-html": "~1.5.0",
"postcss-rtlcss": "~3.7.2", "postcss-rtlcss": "~3.7.2",

View file

@ -35,6 +35,11 @@ textarea.form-control {
color: $maintenance !important; color: $maintenance !important;
} }
.incident a,
.bg-maintenance a {
color: inherit;
}
.list-group { .list-group {
border-radius: 0.75rem; border-radius: 0.75rem;
@ -248,6 +253,11 @@ optgroup {
} }
} }
.incident a,
.bg-maintenance a {
color: inherit;
}
.form-control, .form-control,
.form-control:focus, .form-control:focus,
.form-select, .form-select,

View file

@ -25,6 +25,7 @@
"General Monitor Type": "General Monitor Type", "General Monitor Type": "General Monitor Type",
"Passive Monitor Type": "Passive Monitor Type", "Passive Monitor Type": "Passive Monitor Type",
"Specific Monitor Type": "Specific Monitor Type", "Specific Monitor Type": "Specific Monitor Type",
"markdownSupported": "Markdown syntax supported",
"pauseDashboardHome": "Pause", "pauseDashboardHome": "Pause",
"Pause": "Pause", "Pause": "Pause",
"Name": "Name", "Name": "Name",

View file

@ -21,6 +21,9 @@
<textarea <textarea
id="description" v-model="maintenance.description" class="form-control" id="description" v-model="maintenance.description" class="form-control"
></textarea> ></textarea>
<div class="form-text">
{{ $t("markdownSupported") }}
</div>
</div> </div>
<!-- Affected Monitors --> <!-- Affected Monitors -->

View file

@ -26,6 +26,9 @@
<div class="my-3"> <div class="my-3">
<label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label> <label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label>
<textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea> <textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea>
<div class="form-text">
{{ $t("markdownSupported") }}
</div>
</div> </div>
<div class="my-3 form-check form-switch"> <div class="my-3 form-check form-switch">
@ -148,7 +151,12 @@
<Editable v-model="incident.title" tag="h4" :contenteditable="editIncidentMode" :noNL="true" class="alert-heading" /> <Editable v-model="incident.title" tag="h4" :contenteditable="editIncidentMode" :noNL="true" class="alert-heading" />
<strong v-if="editIncidentMode">{{ $t("Content") }}:</strong> <strong v-if="editIncidentMode">{{ $t("Content") }}:</strong>
<Editable v-model="incident.content" tag="div" :contenteditable="editIncidentMode" class="content" /> <Editable v-if="editIncidentMode" v-model="incident.content" tag="div" :contenteditable="editIncidentMode" class="content" />
<div v-if="editIncidentMode" class="form-text">
{{ $t("markdownSupported") }}
</div>
<!-- eslint-disable-next-line vue/no-v-html-->
<div v-if="! editIncidentMode" class="content" v-html="incidentHTML"></div>
<!-- Incident Date --> <!-- Incident Date -->
<div class="date mt-3"> <div class="date mt-3">
@ -236,7 +244,8 @@
class="shadow-box alert mb-4 p-3 bg-maintenance mt-4 position-relative" role="alert" class="shadow-box alert mb-4 p-3 bg-maintenance mt-4 position-relative" role="alert"
> >
<h4 class="alert-heading">{{ maintenance.title }}</h4> <h4 class="alert-heading">{{ maintenance.title }}</h4>
<div class="content">{{ maintenance.description }}</div> <!-- eslint-disable-next-line vue/no-v-html-->
<div class="content" v-html="maintenanceHTML(maintenance.description)"></div>
<MaintenanceTime :maintenance="maintenance" /> <MaintenanceTime :maintenance="maintenance" />
</div> </div>
</template> </template>
@ -279,7 +288,9 @@
<div class="custom-footer-text text-start"> <div class="custom-footer-text text-start">
<strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong> <strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong>
</div> </div>
<Editable v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" /> <Editable v-if="enableEditMode" v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" />
<!-- eslint-disable-next-line vue/no-v-html-->
<div v-if="! enableEditMode" class="alert-heading p-2" v-html="footerHTML"></div>
<p v-if="config.showPoweredBy"> <p v-if="config.showPoweredBy">
{{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a> {{ $t("Powered by") }} <a target="_blank" rel="noopener noreferrer" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
@ -310,6 +321,8 @@ import ImageCropUpload from "vue-image-crop-upload";
import { PrismEditor } from "vue-prism-editor"; import { PrismEditor } from "vue-prism-editor";
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
import { useToast } from "vue-toastification"; import { useToast } from "vue-toastification";
import { marked } from "marked";
import DOMPurify from "dompurify";
import Confirm from "../components/Confirm.vue"; import Confirm from "../components/Confirm.vue";
import PublicGroupList from "../components/PublicGroupList.vue"; import PublicGroupList from "../components/PublicGroupList.vue";
import MaintenanceTime from "../components/MaintenanceTime.vue"; import MaintenanceTime from "../components/MaintenanceTime.vue";
@ -477,6 +490,13 @@ export default {
return this.overallStatus === STATUS_PAGE_MAINTENANCE; return this.overallStatus === STATUS_PAGE_MAINTENANCE;
}, },
incidentHTML() {
return DOMPurify.sanitize(marked(this.incident.content));
},
footerHTML() {
return DOMPurify.sanitize(marked(this.config.footerText));
},
}, },
watch: { watch: {
@ -836,6 +856,15 @@ export default {
this.config.domainNameList.splice(index, 1); this.config.domainNameList.splice(index, 1);
}, },
/**
* Generate sanitized HTML from maintenance description
* @param {string} description
* @returns {string} Sanitized HTML
*/
maintenanceHTML(description) {
return DOMPurify.sanitize(marked(description));
},
} }
}; };
</script> </script>