mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-21 16:15:25 +08:00
Merge branch 'master' into master
This commit is contained in:
@@ -25,9 +25,9 @@ export default {
|
||||
MonitorList,
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
return {};
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
@@ -118,6 +118,7 @@ export default {
|
||||
return 0;
|
||||
});
|
||||
|
||||
// eslint-disable-next-line vue/no-side-effects-in-computed-properties
|
||||
this.heartBeatList = result;
|
||||
|
||||
return result;
|
||||
|
@@ -30,7 +30,10 @@
|
||||
Push
|
||||
</option>
|
||||
<option value="steam">
|
||||
Steam Game Server
|
||||
{{ $t("Steam Game Server") }}
|
||||
</option>
|
||||
<option value="mqtt">
|
||||
MQTT
|
||||
</option>
|
||||
<option value="docker">
|
||||
{{ $t("Docker Container") }}
|
||||
@@ -56,7 +59,7 @@
|
||||
<CopyableInput id="push-url" v-model="pushURL" type="url" disabled="disabled" />
|
||||
<div class="form-text">
|
||||
{{ $t("needPushEvery", [monitor.interval]) }}<br />
|
||||
{{ $t("pushOptionalParams", ["msg, ping"]) }}
|
||||
{{ $t("pushOptionalParams", ["status, msg, ping"]) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,15 +73,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Hostname -->
|
||||
<!-- TCP Port / Ping / DNS / Steam only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam'" class="my-3">
|
||||
<!-- TCP Port / Ping / DNS / Steam / MQTT only -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'ping' || monitor.type === 'dns' || monitor.type === 'steam' || monitor.type === 'mqtt'" class="my-3">
|
||||
<label for="hostname" class="form-label">{{ $t("Hostname") }}</label>
|
||||
<input id="hostname" v-model="monitor.hostname" type="text" class="form-control" :pattern="`${ipRegexPattern}|${hostnameRegexPattern}`" required>
|
||||
</div>
|
||||
|
||||
<!-- Port -->
|
||||
<!-- For TCP Port / Steam Type -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'steam'" class="my-3">
|
||||
<!-- For TCP Port / Steam / MQTT Type -->
|
||||
<div v-if="monitor.type === 'port' || monitor.type === 'steam' || monitor.type === 'mqtt'" class="my-3">
|
||||
<label for="port" class="form-label">{{ $t("Port") }}</label>
|
||||
<input id="port" v-model="monitor.port" type="number" class="form-control" required min="0" max="65535" step="1">
|
||||
</div>
|
||||
@@ -117,7 +120,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Docker Container Name / ID -->
|
||||
<!-- For Docker Type -->
|
||||
<div v-if="monitor.type === 'docker'" class="my-3">
|
||||
@@ -146,6 +149,36 @@
|
||||
<input id="docker_daemon" v-model="monitor.docker_daemon" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<!-- MQTT -->
|
||||
<!-- For MQTT Type -->
|
||||
<template v-if="monitor.type === 'mqtt'">
|
||||
<div class="my-3">
|
||||
<label for="mqttUsername" class="form-label">MQTT {{ $t("Username") }}</label>
|
||||
<input id="mqttUsername" v-model="monitor.mqttUsername" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="mqttPassword" class="form-label">MQTT {{ $t("Password") }}</label>
|
||||
<input id="mqttPassword" v-model="monitor.mqttPassword" type="password" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="mqttTopic" class="form-label">MQTT {{ $t("Topic") }}</label>
|
||||
<input id="mqttTopic" v-model="monitor.mqttTopic" type="text" class="form-control" required>
|
||||
<div class="form-text">
|
||||
{{ $t("topicExplanation") }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-3">
|
||||
<label for="mqttSuccessMessage" class="form-label">MQTT {{ $t("successMessage") }}</label>
|
||||
<input id="mqttSuccessMessage" v-model="monitor.mqttSuccessMessage" type="text" class="form-control">
|
||||
<div class="form-text">
|
||||
{{ $t("successMessageExplanation") }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Interval -->
|
||||
<div class="my-3">
|
||||
<label for="interval" class="form-label">{{ $t("Heartbeat Interval") }} ({{ $t("checkEverySecond", [ monitor.interval ]) }})</label>
|
||||
@@ -170,10 +203,10 @@
|
||||
|
||||
<h2 v-if="monitor.type !== 'push'" class="mt-5 mb-2">{{ $t("Advanced") }}</h2>
|
||||
|
||||
<div class="my-3 form-check">
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3 form-check">
|
||||
<input id="expiry-notification" v-model="monitor.expiryNotification" class="form-check-input" type="checkbox">
|
||||
<label class="form-check-label" for="expiry-notification">
|
||||
{{ $t("Domain Name Expiry Notification") }}
|
||||
{{ $t("Certificate Expiry Notification") }}
|
||||
</label>
|
||||
<div class="form-text">
|
||||
</div>
|
||||
@@ -359,13 +392,12 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { useToast } from "vue-toastification";
|
||||
import CopyableInput from "../components/CopyableInput.vue";
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import ProxyDialog from "../components/ProxyDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import CopyableInput from "../components/CopyableInput.vue";
|
||||
|
||||
import { useToast } from "vue-toastification";
|
||||
import VueMultiselect from "vue-multiselect";
|
||||
import { genSecret, isDev } from "../util.ts";
|
||||
|
||||
const toast = useToast();
|
||||
@@ -420,21 +452,21 @@ export default {
|
||||
},
|
||||
|
||||
pushURL() {
|
||||
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?msg=OK&ping=";
|
||||
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?status=up&msg=OK&ping=";
|
||||
},
|
||||
|
||||
bodyPlaceholder() {
|
||||
return this.$t("Example:", [`
|
||||
return this.$t("Example:", [ `
|
||||
{
|
||||
"key": "value"
|
||||
}`]);
|
||||
}` ]);
|
||||
},
|
||||
|
||||
headersPlaceholder() {
|
||||
return this.$t("Example:", [`
|
||||
return this.$t("Example:", [ `
|
||||
{
|
||||
"HeaderName": "HeaderValue"
|
||||
}`]);
|
||||
}` ]);
|
||||
}
|
||||
|
||||
},
|
||||
@@ -519,13 +551,17 @@ export default {
|
||||
upsideDown: false,
|
||||
expiryNotification: false,
|
||||
maxredirects: 10,
|
||||
accepted_statuscodes: ["200-299"],
|
||||
accepted_statuscodes: [ "200-299" ],
|
||||
dns_resolve_type: "A",
|
||||
dns_resolve_server: "1.1.1.1",
|
||||
docker_container: "",
|
||||
docker_daemon: "/var/run/docker.sock",
|
||||
docker_type: "socket",
|
||||
proxyId: null,
|
||||
mqttUsername: "",
|
||||
mqttPassword: "",
|
||||
mqttTopic: "",
|
||||
mqttSuccessMessage: "",
|
||||
};
|
||||
|
||||
if (this.$root.proxyList && !this.monitor.proxyId) {
|
||||
@@ -537,7 +573,7 @@ export default {
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
||||
if (this.$root.notificationList[i].isDefault == true) {
|
||||
if (this.$root.notificationList[i].isDefault === true) {
|
||||
this.monitor.notificationIDList[this.$root.notificationList[i].id] = true;
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,6 @@ export default {
|
||||
components: {
|
||||
MonitorList,
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@@ -92,7 +92,6 @@ export default {
|
||||
}
|
||||
|
||||
.info {
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
font-size: 20px;
|
||||
|
@@ -22,16 +22,16 @@
|
||||
</div>
|
||||
|
||||
<div class="guide">
|
||||
Most likely causes:
|
||||
{{ $t("Most likely causes:") }}
|
||||
<ul>
|
||||
<li>The resource is no longer available.</li>
|
||||
<li>There might be a typing error in the address.</li>
|
||||
<li>{{ $t("The resource is no longer available.") }}</li>
|
||||
<li>{{ $t("There might be a typing error in the address.") }}</li>
|
||||
</ul>
|
||||
|
||||
What you can try:<br />
|
||||
{{ $t("What you can try:") }}<br />
|
||||
<ul>
|
||||
<li>Retype the address.</li>
|
||||
<li><a href="#" class="go-back" @click="goBack()">Go back to the previous page.</a></li>
|
||||
<li>{{ $t("Retype the address.") }}</li>
|
||||
<li><a href="#" class="go-back" @click="goBack()">{{ $t("Go back to the previous page.") }}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -16,6 +16,14 @@
|
||||
{{ item.title }}
|
||||
</div>
|
||||
</router-link>
|
||||
|
||||
<!-- Logout Button -->
|
||||
<a v-if="$root.isMobile && $root.loggedIn && $root.socket.token !== 'autoLogin'" class="logout" @click.prevent="$root.logout">
|
||||
<div class="menu-item">
|
||||
<font-awesome-icon icon="sign-out-alt" />
|
||||
{{ $t("Logout") }}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="settings-content col-lg-9 col-md-7">
|
||||
<div v-if="currentPage" class="settings-content-header">
|
||||
@@ -233,4 +241,8 @@ footer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.logout {
|
||||
color: $danger !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -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,15 +38,21 @@
|
||||
<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>
|
||||
<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">
|
||||
</div>
|
||||
|
||||
<!-- Domain Name List -->
|
||||
<div class="my-3">
|
||||
<label class="form-label">
|
||||
Domain Names
|
||||
{{ $t("Domain Names") }}
|
||||
<font-awesome-icon icon="plus-circle" class="btn-add-domain action text-primary" @click="addDomainField" />
|
||||
</label>
|
||||
|
||||
@@ -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" />
|
||||
@@ -85,15 +104,16 @@
|
||||
|
||||
<!-- Uploader -->
|
||||
<!-- url="/api/status-page/upload-logo" -->
|
||||
<ImageCropUpload v-model="showImageCropUpload"
|
||||
field="img"
|
||||
:width="128"
|
||||
:height="128"
|
||||
:langType="$i18n.locale"
|
||||
img-format="png"
|
||||
:noCircle="true"
|
||||
:noSquare="false"
|
||||
@crop-success="cropSuccess"
|
||||
<ImageCropUpload
|
||||
v-model="showImageCropUpload"
|
||||
field="img"
|
||||
:width="128"
|
||||
:height="128"
|
||||
:langType="$i18n.locale"
|
||||
img-format="png"
|
||||
:noCircle="true"
|
||||
:noSquare="false"
|
||||
@crop-success="cropSuccess"
|
||||
/>
|
||||
|
||||
<!-- Title -->
|
||||
@@ -239,31 +259,50 @@
|
||||
</div>
|
||||
|
||||
<footer class="mt-5 mb-4">
|
||||
{{ $t("Powered by") }} <a target="_blank" href="https://github.com/louislam/uptime-kuma">{{ $t("Uptime Kuma" ) }}</a>
|
||||
<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>
|
||||
|
||||
<Confirm ref="confirmDelete" btn-style="btn-danger" :yes-text="$t('Yes')" :no-text="$t('No')" @yes="deleteStatusPage">
|
||||
{{ $t("deleteStatusPageMsg") }}
|
||||
</Confirm>
|
||||
|
||||
<component is="style" v-if="config.customCSS" type="text/css">
|
||||
{{ config.customCSS }}
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from "axios";
|
||||
import PublicGroupList from "../components/PublicGroupList.vue";
|
||||
import ImageCropUpload from "vue-image-crop-upload";
|
||||
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_PARTIAL_DOWN, UP } from "../util.ts";
|
||||
import { useToast } from "vue-toastification";
|
||||
import dayjs from "dayjs";
|
||||
import Favico from "favico.js";
|
||||
import { getResBaseURL } from "../util-frontend";
|
||||
// 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
|
||||
import ImageCropUpload from "vue-image-crop-upload";
|
||||
// import Prism Editor
|
||||
import { PrismEditor } from "vue-prism-editor";
|
||||
import "vue-prism-editor/dist/prismeditor.min.css"; // import the styles somewhere
|
||||
import { useToast } from "vue-toastification";
|
||||
import Confirm from "../components/Confirm.vue";
|
||||
import PublicGroupList from "../components/PublicGroupList.vue";
|
||||
import { getResBaseURL } from "../util-frontend";
|
||||
import { STATUS_PAGE_ALL_DOWN, STATUS_PAGE_ALL_UP, STATUS_PAGE_PARTIAL_DOWN, UP } from "../util.ts";
|
||||
|
||||
const toast = useToast();
|
||||
|
||||
const leavePageMsg = "Do you really want to leave? you have unsaved changes!";
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
let feedInterval;
|
||||
|
||||
const favicon = new Favico({
|
||||
@@ -276,6 +315,7 @@ export default {
|
||||
PublicGroupList,
|
||||
ImageCropUpload,
|
||||
Confirm,
|
||||
PrismEditor,
|
||||
},
|
||||
|
||||
// Leave Page for vue route change
|
||||
@@ -418,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);
|
||||
}
|
||||
@@ -520,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) {
|
||||
@@ -637,7 +688,7 @@ export default {
|
||||
},
|
||||
|
||||
statusPageLogoLoaded(eventPayload) {
|
||||
// Remark: may not work in dev, due to cros
|
||||
// Remark: may not work in dev, due to CORS
|
||||
favicon.image(eventPayload.target);
|
||||
},
|
||||
|
||||
@@ -656,7 +707,7 @@ export default {
|
||||
},
|
||||
|
||||
postIncident() {
|
||||
if (this.incident.title == "" || this.incident.content == "") {
|
||||
if (this.incident.title === "" || this.incident.content === "") {
|
||||
toast.error(this.$t("Please input title and content"));
|
||||
return;
|
||||
}
|
||||
@@ -836,7 +887,7 @@ footer {
|
||||
|
||||
.incident {
|
||||
.content {
|
||||
&[contenteditable=true] {
|
||||
&[contenteditable="true"] {
|
||||
min-height: 60px;
|
||||
}
|
||||
}
|
||||
@@ -893,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>
|
||||
|
Reference in New Issue
Block a user