mirror of
https://github.com/louislam/uptime-kuma.git
synced 2025-08-21 20:02:29 +08:00
add Push-based monitoring (#279)
This commit is contained in:
@@ -180,6 +180,11 @@ h2 {
|
||||
border-color: $dark-border-color;
|
||||
}
|
||||
|
||||
.form-control:disabled, .form-control[readonly] {
|
||||
background-color: #232f3b;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.table-hover > tbody > tr:hover {
|
||||
--bs-table-accent-bg: #070a10;
|
||||
color: $dark-font-color;
|
||||
|
122
src/components/CopyableInput.vue
Normal file
122
src/components/CopyableInput.vue
Normal file
@@ -0,0 +1,122 @@
|
||||
<template>
|
||||
<div class="input-group mb-3">
|
||||
<input
|
||||
:id="id"
|
||||
ref="input"
|
||||
v-model="model"
|
||||
:type="type"
|
||||
class="form-control"
|
||||
:placeholder="placeholder"
|
||||
:autocomplete="autocomplete"
|
||||
:required="required"
|
||||
:readonly="readonly"
|
||||
:disabled="disabled"
|
||||
>
|
||||
|
||||
<a class="btn btn-outline-primary" @click="copyToClipboard(model)">
|
||||
<font-awesome-icon :icon="icon" />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
let timeout;
|
||||
|
||||
export default {
|
||||
props: {
|
||||
id: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: "text"
|
||||
},
|
||||
modelValue: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: ""
|
||||
},
|
||||
autocomplete: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
required: {
|
||||
type: Boolean
|
||||
},
|
||||
readonly: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
disabled: {
|
||||
type: String,
|
||||
default: undefined,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visibility: "password",
|
||||
icon: "copy",
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
model: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
showInput() {
|
||||
this.visibility = "text";
|
||||
},
|
||||
|
||||
hideInput() {
|
||||
this.visibility = "password";
|
||||
},
|
||||
|
||||
copyToClipboard(textToCopy) {
|
||||
this.icon = "check";
|
||||
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(() => {
|
||||
this.icon = "copy";
|
||||
}, 3000);
|
||||
|
||||
// navigator clipboard api needs a secure context (https)
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
// navigator clipboard api method'
|
||||
return navigator.clipboard.writeText(textToCopy);
|
||||
} else {
|
||||
// text area method
|
||||
let textArea = document.createElement("textarea");
|
||||
textArea.value = textToCopy;
|
||||
// make the textarea out of viewport
|
||||
textArea.style.position = "fixed";
|
||||
textArea.style.left = "-999999px";
|
||||
textArea.style.top = "-999999px";
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
return new Promise((res, rej) => {
|
||||
// here the magic happens
|
||||
document.execCommand("copy") ? res() : rej();
|
||||
textArea.remove();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
</script>
|
@@ -26,7 +26,10 @@ import {
|
||||
faArrowsAltV,
|
||||
faUnlink,
|
||||
faQuestionCircle,
|
||||
faImages, faUpload,
|
||||
faImages,
|
||||
faUpload,
|
||||
faCopy,
|
||||
faCheck,
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
library.add(
|
||||
@@ -54,6 +57,8 @@ library.add(
|
||||
faQuestionCircle,
|
||||
faImages,
|
||||
faUpload,
|
||||
faCopy,
|
||||
faCheck,
|
||||
);
|
||||
|
||||
export { FontAwesomeIcon };
|
||||
|
@@ -36,5 +36,13 @@ export default {
|
||||
|
||||
return result;
|
||||
},
|
||||
|
||||
baseURL() {
|
||||
if (env === "development" || localStorage.dev === "dev") {
|
||||
return axios.defaults.baseURL;
|
||||
} else {
|
||||
return location.protocol + "//" + location.host;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -26,19 +26,31 @@
|
||||
<option value="dns">
|
||||
DNS
|
||||
</option>
|
||||
<option value="push">
|
||||
Push
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Friendly Name -->
|
||||
<div class="my-3">
|
||||
<label for="name" class="form-label">{{ $t("Friendly Name") }}</label>
|
||||
<input id="name" v-model="monitor.name" type="text" class="form-control" required>
|
||||
</div>
|
||||
|
||||
<!-- URL -->
|
||||
<div v-if="monitor.type === 'http' || monitor.type === 'keyword' " class="my-3">
|
||||
<label for="url" class="form-label">{{ $t("URL") }}</label>
|
||||
<input id="url" v-model="monitor.url" type="url" class="form-control" pattern="https?://.+" required>
|
||||
</div>
|
||||
|
||||
<!-- Push URL -->
|
||||
<div v-if="monitor.type === 'push' " class="my-3">
|
||||
<label for="push-url" class="form-label">{{ $t("Push URL") }}</label>
|
||||
<CopyableInput id="push-url" v-model="pushURL" type="url" disabled="disabled" />
|
||||
</div>
|
||||
|
||||
<!-- Keyword -->
|
||||
<div v-if="monitor.type === 'keyword' " class="my-3">
|
||||
<label for="keyword" class="form-label">{{ $t("Keyword") }}</label>
|
||||
<input id="keyword" v-model="monitor.keyword" type="text" class="form-control" required>
|
||||
@@ -210,13 +222,17 @@
|
||||
<script>
|
||||
import NotificationDialog from "../components/NotificationDialog.vue";
|
||||
import TagsManager from "../components/TagsManager.vue";
|
||||
import { useToast } from "vue-toastification"
|
||||
import VueMultiselect from "vue-multiselect"
|
||||
import { isDev } from "../util.ts";
|
||||
const toast = useToast()
|
||||
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();
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CopyableInput,
|
||||
NotificationDialog,
|
||||
TagsManager,
|
||||
VueMultiselect,
|
||||
@@ -236,7 +252,7 @@ export default {
|
||||
ipRegexPattern: "((^\\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\\s*$)|(^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$))",
|
||||
// Source: https://stackoverflow.com/questions/106179/regular-expression-to-match-dns-hostname-or-ip-address
|
||||
hostnameRegexPattern: "^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$"
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
@@ -253,23 +269,42 @@ export default {
|
||||
pageName() {
|
||||
return this.$t((this.isAdd) ? "Add New Monitor" : "Edit");
|
||||
},
|
||||
|
||||
isAdd() {
|
||||
return this.$route.path === "/add";
|
||||
},
|
||||
|
||||
isEdit() {
|
||||
return this.$route.path.startsWith("/edit");
|
||||
},
|
||||
|
||||
pushURL() {
|
||||
|
||||
return this.$root.baseURL + "/api/push/" + this.monitor.pushToken + "?msg=OK";
|
||||
}
|
||||
|
||||
},
|
||||
watch: {
|
||||
|
||||
"$route.fullPath"() {
|
||||
this.init();
|
||||
},
|
||||
|
||||
"monitor.interval"(value, oldValue) {
|
||||
// Link interval and retryInerval if they are the same value.
|
||||
if (this.monitor.retryInterval === oldValue) {
|
||||
this.monitor.retryInterval = value;
|
||||
}
|
||||
},
|
||||
|
||||
"monitor.type"() {
|
||||
if (this.monitor.type === "push") {
|
||||
if (! this.monitor.pushToken) {
|
||||
this.monitor.pushToken = genSecret(10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
mounted() {
|
||||
this.init();
|
||||
@@ -320,7 +355,7 @@ export default {
|
||||
accepted_statuscodes: ["200-299"],
|
||||
dns_resolve_type: "A",
|
||||
dns_resolve_server: "1.1.1.1",
|
||||
}
|
||||
};
|
||||
|
||||
for (let i = 0; i < this.$root.notificationList.length; i++) {
|
||||
if (this.$root.notificationList[i].isDefault == true) {
|
||||
@@ -337,9 +372,9 @@ export default {
|
||||
this.monitor.retryInterval = this.monitor.interval;
|
||||
}
|
||||
} else {
|
||||
toast.error(res.msg)
|
||||
toast.error(res.msg);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
@@ -356,13 +391,13 @@ export default {
|
||||
toast.success(res.msg);
|
||||
this.processing = false;
|
||||
this.$root.getMonitorList();
|
||||
this.$router.push("/dashboard/" + res.monitorID)
|
||||
this.$router.push("/dashboard/" + res.monitorID);
|
||||
} else {
|
||||
toast.error(res.msg);
|
||||
this.processing = false;
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
} else {
|
||||
await this.$refs.tagsManager.submit(this.monitor.id);
|
||||
|
||||
@@ -370,7 +405,7 @@ export default {
|
||||
this.processing = false;
|
||||
this.$root.toastRes(res);
|
||||
this.init();
|
||||
})
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -380,7 +415,7 @@ export default {
|
||||
this.monitor.notificationIDList[id] = true;
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
12
src/util.js
12
src/util.js
@@ -7,7 +7,7 @@
|
||||
// Backend uses the compiled file util.js
|
||||
// Frontend uses util.ts
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
|
||||
exports.genSecret = exports.getRandomInt = exports.getRandomArbitrary = exports.TimeLogger = exports.polyfill = exports.debug = exports.ucfirst = exports.sleep = exports.flipStatus = exports.STATUS_PAGE_PARTIAL_DOWN = exports.STATUS_PAGE_ALL_UP = exports.STATUS_PAGE_ALL_DOWN = exports.PENDING = exports.UP = exports.DOWN = exports.appName = exports.isDev = void 0;
|
||||
const _dayjs = require("dayjs");
|
||||
const dayjs = _dayjs;
|
||||
exports.isDev = process.env.NODE_ENV === "development";
|
||||
@@ -102,3 +102,13 @@ function getRandomInt(min, max) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
exports.getRandomInt = getRandomInt;
|
||||
function genSecret(length = 64) {
|
||||
let secret = "";
|
||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let charsLength = chars.length;
|
||||
for (let i = 0; i < length; i++) {
|
||||
secret += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
exports.genSecret = genSecret;
|
||||
|
10
src/util.ts
10
src/util.ts
@@ -113,3 +113,13 @@ export function getRandomInt(min: number, max: number) {
|
||||
max = Math.floor(max);
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
export function genSecret(length = 64) {
|
||||
let secret = "";
|
||||
let chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
let charsLength = chars.length;
|
||||
for ( let i = 0; i < length; i++ ) {
|
||||
secret += chars.charAt(Math.floor(Math.random() * charsLength));
|
||||
}
|
||||
return secret;
|
||||
}
|
||||
|
Reference in New Issue
Block a user