diff --git a/db/patch-status-page-footer-css.sql b/db/patch-status-page-footer-css.sql new file mode 100644 index 000000000..413918f11 --- /dev/null +++ b/db/patch-status-page-footer-css.sql @@ -0,0 +1,6 @@ +-- 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 footer_text TEXT; +ALTER TABLE status_page ADD custom_css TEXT; +ALTER TABLE status_page ADD show_powered_by BOOLEAN NOT NULL DEFAULT 1; +COMMIT; diff --git a/package.json b/package.json index 730359221..305d79b81 100644 --- a/package.json +++ b/package.json @@ -91,6 +91,7 @@ "password-hash": "~1.2.2", "postcss-rtlcss": "~3.4.1", "postcss-scss": "~4.0.3", + "prismjs": "^1.27.0", "prom-client": "~13.2.0", "prometheus-api-metrics": "~3.2.1", "qrcode": "~1.5.0", @@ -110,6 +111,7 @@ "vue-i18n": "~9.1.9", "vue-image-crop-upload": "~3.0.3", "vue-multiselect": "~3.0.0-alpha.2", + "vue-prism-editor": "^2.0.0-alpha.2", "vue-qrcode": "~1.0.0", "vue-router": "~4.0.14", "vue-toastification": "~2.0.0-rc.5", diff --git a/server/database.js b/server/database.js index b398101e0..330c24369 100644 --- a/server/database.js +++ b/server/database.js @@ -56,6 +56,7 @@ class Database { "patch-status-page.sql": true, "patch-proxy.sql": true, "patch-monitor-expiry-notification.sql": true, + "patch-status-page-footer-css.sql": true, } /** diff --git a/server/model/status_page.js b/server/model/status_page.js index 1383d3b00..b1befc258 100644 --- a/server/model/status_page.js +++ b/server/model/status_page.js @@ -92,6 +92,9 @@ class StatusPage extends BeanModel { published: !!this.published, showTags: !!this.show_tags, domainNameList: this.getDomainNameList(), + customCSS: this.custom_css, + footerText: this.footer_text, + showPoweredBy: !!this.show_powered_by, }; } @@ -104,6 +107,9 @@ class StatusPage extends BeanModel { theme: this.theme, published: !!this.published, showTags: !!this.show_tags, + customCSS: this.custom_css, + footerText: this.footer_text, + showPoweredBy: !!this.show_powered_by, }; } diff --git a/server/socket-handlers/status-page-socket-handler.js b/server/socket-handlers/status-page-socket-handler.js index 36e90fb93..a06271da5 100644 --- a/server/socket-handlers/status-page-socket-handler.js +++ b/server/socket-handlers/status-page-socket-handler.js @@ -155,6 +155,9 @@ module.exports.statusPageSocketHandler = (socket) => { //statusPage.search_engine_index = ; statusPage.show_tags = config.showTags; //statusPage.password = null; + statusPage.footer_text = config.footerText; + statusPage.custom_css = config.customCSS; + statusPage.show_powered_by = config.showPoweredBy; statusPage.modified_date = R.isoDateTime(); await R.store(statusPage); diff --git a/src/assets/app.scss b/src/assets/app.scss index 0b27c6a6e..c3f2fa798 100644 --- a/src/assets/app.scss +++ b/src/assets/app.scss @@ -469,6 +469,10 @@ textarea.form-control { color: $primary; } +.prism-editor__textarea { + outline: none !important; +} + // Localization @import "localization.scss"; diff --git a/src/languages/de-DE.js b/src/languages/de-DE.js index 63531dfaf..1dd86e23a 100644 --- a/src/languages/de-DE.js +++ b/src/languages/de-DE.js @@ -443,6 +443,6 @@ export default { "Fingerprint:": "Fingerabdruck:", "No status pages": "Keine Status-Seiten", Customize: "Anpassen", - "Custom Footer": "Eigener Footer (Leerlassen für Standard)", + "Custom Footer": "Eigener Footer", "Custom CSS": "Eigenes CSS", }; diff --git a/src/languages/en.js b/src/languages/en.js index 957a13560..1061f426c 100644 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -355,7 +355,7 @@ export default { serwersmsSenderName: "SMS Sender Name (registered via customer portal)", stackfield: "Stackfield", Customize: "Customize", - "Custom Footer": "Custom Footer (empty string for default)", + "Custom Footer": "Custom Footer", "Custom CSS": "Custom CSS", smtpDkimSettings: "DKIM Settings", smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.", diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index 1b842c3d5..da0bafb5e 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -16,11 +16,18 @@ <input id="title" v-model="config.title" type="text" class="form-control"> </div> + <!-- Description --> <div class="my-3"> <label for="description" class="form-label">{{ $t("Description") }}</label> <textarea id="description" v-model="config.description" class="form-control"></textarea> </div> + <!-- Footer Text --> + <div class="my-3"> + <label for="footer-text" class="form-label">{{ $t("Footer Text") }}</label> + <textarea id="footer-text" v-model="config.footerText" class="form-control"></textarea> + </div> + <div class="my-3 form-check form-switch"> <input id="switch-theme" v-model="config.theme" class="form-check-input" type="checkbox" true-value="dark" false-value="light"> <label class="form-check-label" for="switch-theme">{{ $t("Switch to Dark Theme") }}</label> @@ -31,6 +38,12 @@ <label class="form-check-label" for="showTags">{{ $t("Show Tags") }}</label> </div> + <!-- Show Powered By --> + <div class="my-3 form-check form-switch"> + <input id="show-powered-by" v-model="config.showPoweredBy" class="form-check-input" type="checkbox"> + <label class="form-check-label" for="show-powered-by">{{ $t("Show Powered By") }}</label> + </div> + <div v-if="false" class="my-3"> <label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label> <input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control"> @@ -51,6 +64,12 @@ </ul> </div> + <!-- Custom CSS --> + <div class="my-3"> + <div class="mb-1">{{ $t("Custom CSS") }}</div> + <prism-editor v-model="config.customCSS" class="css-editor" :highlight="highlighter" line-numbers></prism-editor> + </div> + <div class="danger-zone"> <button class="btn btn-danger me-2" @click="deleteDialog"> <font-awesome-icon icon="trash" /> @@ -119,11 +138,6 @@ <font-awesome-icon icon="bullhorn" /> {{ $t("Create Incident") }} </button> - - <button class="btn btn-primary me-2" @click="customizeStatusPage"> - <font-awesome-icon icon="cog" /> - {{ $t("Customize") }} - </button> </div> </div> @@ -180,15 +194,6 @@ </div> </div> - <!-- Customize --> - <div v-if="editMode && enableEditCustomizeMode" class="mb-4 p-4 alert shadow-box customize" role="alert"> - <strong v-if="enableEditCustomizeMode">{{ $t("Custom CSS") }}:</strong> - <Editable v-model="config.customCSS" tag="div" :contenteditable="enableEditCustomizeMode" class="content p-2" /> - <br /> - <strong v-if="enableEditCustomizeMode">{{ $t("Custom Footer") }}:</strong> - <Editable v-model="config.poweredBy" tag="h4" :contenteditable="enableEditCustomizeMode" :noNL="true" class="alert-heading p-2" /> - </div> - <!-- Overall Status --> <div class="shadow-box list p-4 overall-status mb-4"> <div v-if="Object.keys($root.publicMonitorList).length === 0 && loadedData"> @@ -253,8 +258,14 @@ </div> <footer class="mt-5 mb-4"> - <p v-if="config.poweredBy" v-html="config.poweredBy"></p> - <p v-else>{{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a></p> + <div class="custom-footer-text text-start"> + <strong v-if="enableEditMode">{{ $t("Custom Footer") }}:</strong> + </div> + <Editable v-model="config.footerText" tag="div" :contenteditable="enableEditMode" :noNL="false" class="alert-heading p-2" /> + + <p v-if="config.showPoweredBy"> + {{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a> + </p> </footer> </div> @@ -278,6 +289,14 @@ import dayjs from "dayjs"; import Favico from "favico.js"; import { getResBaseURL } from "../util-frontend"; import Confirm from "../components/Confirm.vue"; +// import Prism Editor +import { PrismEditor } from "vue-prism-editor"; +import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere + +// import highlighting library (you can use any library you want just return html string) +import { highlight, languages } from "prismjs/components/prism-core"; +import "prismjs/components/prism-css"; +import "prismjs/themes/prism-tomorrow.css"; // import syntax highlighting styles const toast = useToast(); @@ -296,6 +315,7 @@ export default { PublicGroupList, ImageCropUpload, Confirm, + PrismEditor, }, // Leave Page for vue route change @@ -324,7 +344,6 @@ export default { slug: null, enableEditMode: false, enableEditIncidentMode: false, - enableEditCustomizeMode: false, hasToken: false, config: {}, selectedMonitor: null, @@ -439,6 +458,13 @@ export default { this.$root.getSocket().emit("getStatusPage", this.slug, (res) => { if (res.ok) { this.config = res.config; + + if (!this.config.customCSS) { + this.config.customCSS = "body {\n" + + " \n" + + "}\n"; + } + } else { toast.error(res.msg); } @@ -541,6 +567,10 @@ export default { }, methods: { + highlighter(code) { + return highlight(code, languages.css); + }, + updateHeartbeatList() { // If editMode, it will use the data from websocket. if (! this.editMode) { @@ -726,14 +756,6 @@ export default { this.config.domainNameList.splice(index, 1); }, - /** customize status page */ - customizeStatusPage() { - if (this.editMode) { - // toggle modal - this.enableEditCustomizeMode = !this.enableEditCustomizeMode; - } - }, - } }; </script> @@ -863,7 +885,7 @@ footer { } } -.incident, .customize { +.incident { .content { &[contenteditable="true"] { min-height: 60px; @@ -922,4 +944,19 @@ footer { } } +/* required class */ +.css-editor { + /* we dont use `language-` classes anymore so thats why we need to add background and text color manually */ + + border-radius: 1rem; + padding: 10px 5px; + border: 1px solid #ced4da; + + .dark & { + background: $dark-bg; + border: 1px solid $dark-border-color; + } + +} + </style>