Merge pull request #1083 from patrickhafner/customstatuspage

Custom status page
pull/875/head
Louis Lam 3 years ago committed by GitHub
commit cd3dfd3146
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -44,6 +44,7 @@ module.exports = {
"vue/max-attributes-per-line": "off", "vue/max-attributes-per-line": "off",
"vue/singleline-html-element-content-newline": "off", "vue/singleline-html-element-content-newline": "off",
"vue/html-self-closing": "off", "vue/html-self-closing": "off",
"vue/require-component-is": "off", // not allow is="style" https://github.com/vuejs/eslint-plugin-vue/issues/462#issuecomment-430234675
"vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly "vue/attribute-hyphenation": "off", // This change noNL to "no-n-l" unexpectedly
"no-multi-spaces": ["error", { "no-multi-spaces": ["error", {
ignoreEOLComments: true, ignoreEOLComments: true,

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

@ -91,6 +91,7 @@
"password-hash": "~1.2.2", "password-hash": "~1.2.2",
"postcss-rtlcss": "~3.4.1", "postcss-rtlcss": "~3.4.1",
"postcss-scss": "~4.0.3", "postcss-scss": "~4.0.3",
"prismjs": "^1.27.0",
"prom-client": "~13.2.0", "prom-client": "~13.2.0",
"prometheus-api-metrics": "~3.2.1", "prometheus-api-metrics": "~3.2.1",
"qrcode": "~1.5.0", "qrcode": "~1.5.0",
@ -110,6 +111,7 @@
"vue-i18n": "~9.1.9", "vue-i18n": "~9.1.9",
"vue-image-crop-upload": "~3.0.3", "vue-image-crop-upload": "~3.0.3",
"vue-multiselect": "~3.0.0-alpha.2", "vue-multiselect": "~3.0.0-alpha.2",
"vue-prism-editor": "^2.0.0-alpha.2",
"vue-qrcode": "~1.0.0", "vue-qrcode": "~1.0.0",
"vue-router": "~4.0.14", "vue-router": "~4.0.14",
"vue-toastification": "~2.0.0-rc.5", "vue-toastification": "~2.0.0-rc.5",

@ -56,6 +56,7 @@ class Database {
"patch-status-page.sql": true, "patch-status-page.sql": true,
"patch-proxy.sql": true, "patch-proxy.sql": true,
"patch-monitor-expiry-notification.sql": true, "patch-monitor-expiry-notification.sql": true,
"patch-status-page-footer-css.sql": true,
} }
/** /**

@ -92,6 +92,9 @@ class StatusPage extends BeanModel {
published: !!this.published, published: !!this.published,
showTags: !!this.show_tags, showTags: !!this.show_tags,
domainNameList: this.getDomainNameList(), 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, theme: this.theme,
published: !!this.published, published: !!this.published,
showTags: !!this.show_tags, showTags: !!this.show_tags,
customCSS: this.custom_css,
footerText: this.footer_text,
showPoweredBy: !!this.show_powered_by,
}; };
} }

@ -155,6 +155,9 @@ module.exports.statusPageSocketHandler = (socket) => {
//statusPage.search_engine_index = ; //statusPage.search_engine_index = ;
statusPage.show_tags = config.showTags; statusPage.show_tags = config.showTags;
//statusPage.password = null; //statusPage.password = null;
statusPage.footer_text = config.footerText;
statusPage.custom_css = config.customCSS;
statusPage.show_powered_by = config.showPoweredBy;
statusPage.modified_date = R.isoDateTime(); statusPage.modified_date = R.isoDateTime();
await R.store(statusPage); await R.store(statusPage);

@ -469,6 +469,10 @@ textarea.form-control {
color: $primary; color: $primary;
} }
.prism-editor__textarea {
outline: none !important;
}
// Localization // Localization
@import "localization.scss"; @import "localization.scss";

@ -337,7 +337,7 @@ export default {
"Hide Tags": "Tags ausblenden", "Hide Tags": "Tags ausblenden",
Description: "Beschreibung", Description: "Beschreibung",
"No monitors available.": "Keine Monitore verfügbar.", "No monitors available.": "Keine Monitore verfügbar.",
"Add one": "Füge eins hinzu", "Add one": "Hinzufügen",
"No Monitors": "Keine Monitore", "No Monitors": "Keine Monitore",
"Untitled Group": "Gruppe ohne Titel", "Untitled Group": "Gruppe ohne Titel",
Services: "Dienste", Services: "Dienste",
@ -442,4 +442,7 @@ export default {
"Issuer:": "Aussteller:", "Issuer:": "Aussteller:",
"Fingerprint:": "Fingerabdruck:", "Fingerprint:": "Fingerabdruck:",
"No status pages": "Keine Status-Seiten", "No status pages": "Keine Status-Seiten",
Customize: "Anpassen",
"Custom Footer": "Eigener Footer",
"Custom CSS": "Eigenes CSS",
}; };

@ -354,6 +354,9 @@ export default {
serwersmsPhoneNumber: "Phone number", serwersmsPhoneNumber: "Phone number",
serwersmsSenderName: "SMS Sender Name (registered via customer portal)", serwersmsSenderName: "SMS Sender Name (registered via customer portal)",
stackfield: "Stackfield", stackfield: "Stackfield",
Customize: "Customize",
"Custom Footer": "Custom Footer",
"Custom CSS": "Custom CSS",
smtpDkimSettings: "DKIM Settings", smtpDkimSettings: "DKIM Settings",
smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.", smtpDkimDesc: "Please refer to the Nodemailer DKIM {0} for usage.",
documentation: "documentation", documentation: "documentation",

@ -16,11 +16,18 @@
<input id="title" v-model="config.title" type="text" class="form-control"> <input id="title" v-model="config.title" type="text" class="form-control">
</div> </div>
<!-- Description -->
<div class="my-3"> <div class="my-3">
<label for="description" class="form-label">{{ $t("Description") }}</label> <label for="description" class="form-label">{{ $t("Description") }}</label>
<textarea id="description" v-model="config.description" class="form-control"></textarea> <textarea id="description" v-model="config.description" class="form-control"></textarea>
</div> </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"> <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"> <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> <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> <label class="form-check-label" for="showTags">{{ $t("Show Tags") }}</label>
</div> </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"> <div v-if="false" class="my-3">
<label for="password" class="form-label">{{ $t("Password") }} <sup>Coming Soon</sup></label> <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"> <input id="password" v-model="config.password" disabled type="password" autocomplete="new-password" class="form-control">
@ -51,6 +64,12 @@
</ul> </ul>
</div> </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"> <div class="danger-zone">
<button class="btn btn-danger me-2" @click="deleteDialog"> <button class="btn btn-danger me-2" @click="deleteDialog">
<font-awesome-icon icon="trash" /> <font-awesome-icon icon="trash" />
@ -239,13 +258,24 @@
</div> </div>
<footer class="mt-5 mb-4"> <footer class="mt-5 mb-4">
<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> {{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
</p>
</footer> </footer>
</div> </div>
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteStatusPage"> <Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteStatusPage">
{{ $t("deleteStatusPageMsg") }} {{ $t("deleteStatusPageMsg") }}
</Confirm> </Confirm>
<component is="style" v-if="config.customCSS" type="text/css">
{{ config.customCSS }}
</component>
</div> </div>
</template> </template>
@ -259,6 +289,14 @@ import dayjs from "dayjs";
import Favico from "favico.js"; import Favico from "favico.js";
import { getResBaseURL } from "../util-frontend"; import { getResBaseURL } from "../util-frontend";
import Confirm from "../components/Confirm.vue"; 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(); const toast = useToast();
@ -277,6 +315,7 @@ export default {
PublicGroupList, PublicGroupList,
ImageCropUpload, ImageCropUpload,
Confirm, Confirm,
PrismEditor,
}, },
// Leave Page for vue route change // Leave Page for vue route change
@ -419,6 +458,13 @@ export default {
this.$root.getSocket().emit("getStatusPage", this.slug, (res) => { this.$root.getSocket().emit("getStatusPage", this.slug, (res) => {
if (res.ok) { if (res.ok) {
this.config = res.config; this.config = res.config;
if (!this.config.customCSS) {
this.config.customCSS = "body {\n" +
" \n" +
"}\n";
}
} else { } else {
toast.error(res.msg); toast.error(res.msg);
} }
@ -521,6 +567,10 @@ export default {
}, },
methods: { methods: {
highlighter(code) {
return highlight(code, languages.css);
},
updateHeartbeatList() { updateHeartbeatList() {
// If editMode, it will use the data from websocket. // If editMode, it will use the data from websocket.
if (! this.editMode) { if (! this.editMode) {
@ -894,4 +944,18 @@ 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> </style>

Loading…
Cancel
Save