Compare commits

...

33 Commits

Author SHA1 Message Date
Louis Lam
dd09351c8e Update to 1.17.0-beta.1 2022-06-16 19:34:47 +08:00
Louis Lam
ffad990ca4 Merge pull request #1773 from christopherpickering/patch-3
[beta] updated translation for "basic" auth
2022-06-16 14:37:05 +08:00
Christopher Pickering
42848bcd2e updated translations 2022-06-15 09:00:47 -05:00
Louis Lam
a3b94aa532 Merge pull request #1550 from Computroniks/jsdoc-for-src
JSDoc for src/*/*
2022-06-15 19:29:51 +08:00
Louis Lam
fdbdf83a0d Fix data type of notification.isDefault and notification.active (#1765) 2022-06-15 19:11:52 +08:00
Louis Lam
81d5360520 Merge remote-tracking branch 'origin/master' 2022-06-15 19:03:22 +08:00
Louis Lam
8f1e193de3 [vite] Change legacy browse target to since 2015 2022-06-15 19:02:55 +08:00
Louis Lam
da91317760 Merge pull request #1772 from chakflying/fix/mobile-monitor-list
Fix: Fix monitor list layout on mobile
2022-06-15 15:50:04 +08:00
Louis Lam
bef0febede Merge pull request #1768 from christopherpickering/patch-1
[beta] prevent null workstation #'s from passing..
2022-06-15 15:33:03 +08:00
Louis Lam
7d63b700e1 Merge pull request #1767 from christopherpickering/patch-2
[beta] workstation field should be text, not password
2022-06-15 15:31:24 +08:00
Louis Lam
0223f86a2a Merge remote-tracking branch 'origin/master' 2022-06-15 15:18:29 +08:00
Louis Lam
c170b1edd0 Better optgroup text color 2022-06-15 15:18:14 +08:00
Louis Lam
5943514a92 Merge pull request #1771 from christopherpickering/master
[beta] added default value for sql server connection string
2022-06-15 14:08:48 +08:00
Nelson Chan
62acd2edb1 Fix: misc. layout fix on mobile 2022-06-14 22:43:44 +08:00
Christopher Pickering
483cbfb636 added default value for sql server connection string 2022-06-14 09:00:23 -05:00
Christopher Pickering
660005b143 cleaned up code 2022-06-14 08:49:36 -05:00
Christopher Pickering
98f3c126e5 passed lint 2022-06-14 07:58:35 -05:00
sur.la.route
6995a29980 workstation field should be text, not password 2022-06-14 07:45:04 -05:00
sur.la.route
cf2ca71dee prevent null workstation #'s from passing..
to axios-ntlm
2022-06-14 07:42:53 -05:00
Louis Lam
0bd1c42080 Merge pull request #1763 from chakflying/fix/cert-exp-setting-default
Fix: Fix missing certificate exp. notif. default
2022-06-14 17:27:45 +08:00
Louis Lam
9b21b86e70 Merge pull request #1762 from kaysond/master
Fix upside down push monitors
2022-06-14 17:25:32 +08:00
Nelson Chan
f723930d11 Fix: Unify design with Security page 2022-06-14 15:04:46 +08:00
Nelson Chan
e425e408a2 Fix: Fix missing default settings 2022-06-14 15:04:14 +08:00
Aram Akhavan
c690d1c3a1 fix timeout bypass for upside down push monitor 2022-06-13 22:05:58 -07:00
Matthew Nickson
a927f5cd15 Fixed typos + improved clarity and detail of some JSDoc
Apply suggestions from code review

Co-authored-by: Nelson Chan <chakflying@hotmail.com>
2022-06-02 16:40:56 +01:00
Matthew Nickson
0e28707307 Minor formatting for JSDoc comments
Added a number of minor formatting changes to JSDoc comments in /src
2022-06-02 15:15:21 +01:00
Matthew Nickson
c94dcf1533 Added JSDoc for src/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 14:32:38 +01:00
Matthew Nickson
b0476cfb5b Added JSDoc for src/pages/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 13:46:44 +01:00
Matthew Nickson
2170229031 Improve JSDoc for some components
Apply suggestions from code review

Co-authored-by: Nelson Chan <chakflying@hotmail.com>
2022-06-02 10:42:37 +01:00
Matthew Nickson
213aca4fc3 Added JSDoc for src/mixins/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 10:38:17 +01:00
Matthew Nickson
2b42c3c828 Added JSDoc for src/components/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-02 00:32:05 +01:00
Matthew Nickson
d939d03690 Added JSDoc for src/components/settings/*
Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com>
2022-06-01 23:44:10 +01:00
Matthew Nickson
07888e43f1 [empty commit] pull request for JSDoc src/* 2022-06-01 22:51:26 +01:00
60 changed files with 641 additions and 74 deletions

View File

@@ -14,8 +14,7 @@ export default defineConfig({
plugins: [ plugins: [
vue(), vue(),
legacy({ legacy({
targets: [ "ie > 11" ], targets: [ "since 2015" ],
additionalLegacyPolyfills: [ "regenerator-runtime/runtime" ]
}), }),
visualizer({ visualizer({
filename: "tmp/dist-stats.html" filename: "tmp/dist-stats.html"

View File

@@ -12,7 +12,8 @@ RUN apt update && \
apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \ apt --yes --no-install-recommends install python3 python3-pip python3-cryptography python3-six python3-yaml python3-click python3-markdown python3-requests python3-requests-oauthlib \
sqlite3 iputils-ping util-linux dumb-init && \ sqlite3 iputils-ping util-linux dumb-init && \
pip3 --no-cache-dir install apprise==0.9.8.3 && \ pip3 --no-cache-dir install apprise==0.9.8.3 && \
rm -rf /var/lib/apt/lists/* rm -rf /var/lib/apt/lists/* && \
apt --yes autoremove
# Install cloudflared # Install cloudflared
# dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583 # dpkg --add-architecture arm: cloudflared do not provide armhf, this is workaround. Read more: https://github.com/cloudflare/cloudflared/issues/583
@@ -22,5 +23,6 @@ RUN node ./extra/download-cloudflared.js $TARGETPLATFORM && \
apt update && \ apt update && \
apt --yes --no-install-recommends install ./cloudflared.deb && \ apt --yes --no-install-recommends install ./cloudflared.deb && \
rm -rf /var/lib/apt/lists/* && \ rm -rf /var/lib/apt/lists/* && \
rm -f cloudflared.deb rm -f cloudflared.deb && \
apt --yes autoremove

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.16.1", "version": "1.17.0-beta.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.16.1", "version": "1.17.0-beta.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "~1.2.36", "@fortawesome/fontawesome-svg-core": "~1.2.36",

View File

@@ -1,6 +1,6 @@
{ {
"name": "uptime-kuma", "name": "uptime-kuma",
"version": "1.17.0-beta.0", "version": "1.17.0-beta.1",
"license": "MIT", "license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -22,7 +22,10 @@ async function sendNotificationList(socket) {
]); ]);
for (let bean of list) { for (let bean of list) {
result.push(bean.export()); let notificationObject = bean.export();
notificationObject.isDefault = (notificationObject.isDefault === 1);
notificationObject.active = (notificationObject.active === 1);
result.push(notificationObject);
} }
io.to(socket.userID).emit("notificationList", result); io.to(socket.userID).emit("notificationList", result);

View File

@@ -283,7 +283,7 @@ class Monitor extends BeanModel {
username: this.basic_auth_user, username: this.basic_auth_user,
password: this.basic_auth_pass, password: this.basic_auth_pass,
domain: this.authDomain, domain: this.authDomain,
workstation: this.authWorkstation, workstation: this.authWorkstation ? this.authWorkstation : undefined
}); });
} else { } else {
@@ -406,7 +406,7 @@ class Monitor extends BeanModel {
// If the previous beat was down or pending we use the regular // If the previous beat was down or pending we use the regular
// beatInterval/retryInterval in the setTimeout further below // beatInterval/retryInterval in the setTimeout further below
if (previousBeat.status !== UP || msSinceLastBeat > beatInterval * 1000 + bufferTime) { if (previousBeat.status !== (this.isUpsideDown() ? DOWN : UP) || msSinceLastBeat > beatInterval * 1000 + bufferTime) {
throw new Error("No heartbeat in the time window"); throw new Error("No heartbeat in the time window");
} else { } else {
let timeout = beatInterval * 1000 - msSinceLastBeat; let timeout = beatInterval * 1000 - msSinceLastBeat;

View File

@@ -34,6 +34,25 @@ textarea.form-control {
} }
} }
// optgroup
optgroup {
color: #b1b1b1;
option {
color: #212529;
}
}
.dark {
optgroup {
color: #535864;
option {
color: $dark-font-color;
}
}
}
// Scrollbar
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: #ccc; background: #ccc;
border-radius: 20px; border-radius: 20px;
@@ -363,6 +382,12 @@ textarea.form-control {
overflow-y: auto; overflow-y: auto;
height: calc(100% - 65px); height: calc(100% - 65px);
} }
@media (max-width: 770px) {
&.scrollbar {
height: calc(100% - 40px);
}
}
.item { .item {
display: block; display: block;
@@ -473,6 +498,14 @@ textarea.form-control {
outline: none !important; outline: none !important;
} }
h5.settings-subheading::after {
content: "";
display: block;
width: 50%;
padding-top: 8px;
border-bottom: 1px solid $dark-border-color;
}
// Localization // Localization
@import "localization.scss"; @import "localization.scss";

View File

@@ -25,10 +25,12 @@ export default {
CertificateInfoRow, CertificateInfoRow,
}, },
props: { props: {
/** Object representing certificate */
certInfo: { certInfo: {
type: Object, type: Object,
required: true, required: true,
}, },
/** Is the TLS certificate valid? */
valid: { valid: {
type: Boolean, type: Boolean,
required: true, required: true,

View File

@@ -56,12 +56,19 @@ export default {
Datetime, Datetime,
}, },
props: { props: {
/** Object representing certificate */
cert: { cert: {
type: Object, type: Object,
required: true, required: true,
}, },
}, },
methods: { methods: {
/**
* Format the subject of the certificate
* @param {Object} subject Object representing the certificates
* subject
* @returns {string}
*/
formatSubject(subject) { formatSubject(subject) {
if (subject.O && subject.CN && subject.C) { if (subject.O && subject.CN && subject.C) {
return `${subject.CN} - ${subject.O} (${subject.C})`; return `${subject.CN} - ${subject.O} (${subject.C})`;

View File

@@ -29,14 +29,17 @@ import { Modal } from "bootstrap";
export default { export default {
props: { props: {
/** Style of button */
btnStyle: { btnStyle: {
type: String, type: String,
default: "btn-primary", default: "btn-primary",
}, },
/** Text to use as yes */
yesText: { yesText: {
type: String, type: String,
default: "Yes", // TODO: No idea what to translate this default: "Yes", // TODO: No idea what to translate this
}, },
/** Text to use as no */
noText: { noText: {
type: String, type: String,
default: "No", default: "No",
@@ -50,9 +53,13 @@ export default {
this.modal = new Modal(this.$refs.modal); this.modal = new Modal(this.$refs.modal);
}, },
methods: { methods: {
/** Show the confirm dialog */
show() { show() {
this.modal.show(); this.modal.show();
}, },
/**
* @emits string "yes" Notify the parent when Yes is pressed
*/
yes() { yes() {
this.$emit("yes"); this.$emit("yes");
}, },

View File

@@ -25,33 +25,41 @@ let timeout;
export default { export default {
props: { props: {
/** ID of this input */
id: { id: {
type: String, type: String,
default: "" default: ""
}, },
/** Type of input */
type: { type: {
type: String, type: String,
default: "text" default: "text"
}, },
/** The value of the input */
modelValue: { modelValue: {
type: String, type: String,
default: "" default: ""
}, },
/** A placeholder to use */
placeholder: { placeholder: {
type: String, type: String,
default: "" default: ""
}, },
/** Should the field auto complete */
autocomplete: { autocomplete: {
type: String, type: String,
default: undefined, default: undefined,
}, },
/** Is the input required? */
required: { required: {
type: Boolean type: Boolean
}, },
/** Should the input be read only? */
readonly: { readonly: {
type: String, type: String,
default: undefined, default: undefined,
}, },
/** Is the input disabled? */
disabled: { disabled: {
type: String, type: String,
default: undefined, default: undefined,
@@ -79,14 +87,21 @@ export default {
}, },
methods: { methods: {
/** Show the input */
showInput() { showInput() {
this.visibility = "text"; this.visibility = "text";
}, },
/** Hide the input */
hideInput() { hideInput() {
this.visibility = "password"; this.visibility = "password";
}, },
/**
* Copy the provided text to the users clipboard
* @param {string} textToCopy
* @returns {Promise<void>}
*/
copyToClipboard(textToCopy) { copyToClipboard(textToCopy) {
this.icon = "check"; this.icon = "check";

View File

@@ -10,6 +10,7 @@ import { sleep } from "../util.ts";
export default { export default {
props: { props: {
/** Value to count */
value: { value: {
type: [ String, Number ], type: [ String, Number ],
default: 0, default: 0,
@@ -18,6 +19,7 @@ export default {
type: Number, type: Number,
default: 0.3, default: 0.3,
}, },
/** Unit of the value */
unit: { unit: {
type: String, type: String,
default: "ms", default: "ms",
@@ -43,9 +45,7 @@ export default {
let frames = 12; let frames = 12;
let step = Math.floor(diff / frames); let step = Math.floor(diff / frames);
if (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0) { if (! (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0)) {
// Lazy to NOT this condition, hahaha.
} else {
for (let i = 1; i < frames; i++) { for (let i = 1; i < frames; i++) {
this.output += step; this.output += step;
await sleep(15); await sleep(15);

View File

@@ -13,10 +13,12 @@ dayjs.extend(relativeTime);
export default { export default {
props: { props: {
/** Value of date time */
value: { value: {
type: String, type: String,
default: null, default: null,
}, },
/** Should only the date be displayed? */
dateOnly: { dateOnly: {
type: Boolean, type: Boolean,
default: false, default: false,

View File

@@ -17,14 +17,17 @@
export default { export default {
props: { props: {
/** Size of the heartbeat bar */
size: { size: {
type: String, type: String,
default: "big", default: "big",
}, },
/** ID of the monitor */
monitorId: { monitorId: {
type: Number, type: Number,
required: true, required: true,
}, },
/** Array of the monitors heartbeats */
heartbeatList: { heartbeatList: {
type: Array, type: Array,
default: null, default: null,
@@ -160,12 +163,19 @@ export default {
this.resize(); this.resize();
}, },
methods: { methods: {
/** Resize the heartbeat bar */
resize() { resize() {
if (this.$refs.wrap) { if (this.$refs.wrap) {
this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)); this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2));
} }
}, },
/**
* Get the title of the beat.
* Used as the hover tooltip on the heartbeat bar.
* @param {Object} beat Beat to get title from
* @returns {string}
*/
getBeatTitle(beat) { getBeatTitle(beat) {
return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ""); return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : "");
}, },

View File

@@ -24,25 +24,31 @@
<script> <script>
export default { export default {
props: { props: {
/** The value of the input */
modelValue: { modelValue: {
type: String, type: String,
default: "" default: ""
}, },
/** A placeholder to use */
placeholder: { placeholder: {
type: String, type: String,
default: "" default: ""
}, },
/** Maximum length of the input */
maxlength: { maxlength: {
type: Number, type: Number,
default: 255 default: 255
}, },
/** Should the field auto complete */
autocomplete: { autocomplete: {
type: String, type: String,
default: undefined, default: undefined,
}, },
/** Is the input required? */
required: { required: {
type: Boolean type: Boolean
}, },
/** Should the input be read only? */
readonly: { readonly: {
type: String, type: String,
default: undefined, default: undefined,
@@ -68,9 +74,11 @@ export default {
}, },
methods: { methods: {
/** Show users input in plain text */
showInput() { showInput() {
this.visibility = "text"; this.visibility = "text";
}, },
/** Censor users input */
hideInput() { hideInput() {
this.visibility = "password"; this.visibility = "password";
}, },

View File

@@ -55,6 +55,7 @@ export default {
}; };
}, },
methods: { methods: {
/** Submit the user details and attempt to log in */
submit() { submit() {
this.processing = true; this.processing = true;

View File

@@ -58,6 +58,7 @@ export default {
Tag, Tag,
}, },
props: { props: {
/** Should the scrollbar be shown */
scrollbar: { scrollbar: {
type: Boolean, type: Boolean,
}, },
@@ -69,10 +70,22 @@ export default {
}; };
}, },
computed: { computed: {
/**
* Improve the sticky appearance of the list by increasing its
* height as user scrolls down.
* Not used on mobile.
*/
boxStyle() { boxStyle() {
return { if (window.innerWidth > 550) {
height: `calc(100vh - 160px + ${this.windowTop}px)`, return {
}; height: `calc(100vh - 160px + ${this.windowTop}px)`,
};
} else {
return {
height: "calc(100vh - 160px)",
};
}
}, },
sortedMonitorList() { sortedMonitorList() {
@@ -124,6 +137,7 @@ export default {
window.removeEventListener("scroll", this.onScroll); window.removeEventListener("scroll", this.onScroll);
}, },
methods: { methods: {
/** Handle user scroll */
onScroll() { onScroll() {
if (window.top.scrollY <= 133) { if (window.top.scrollY <= 133) {
this.windowTop = window.top.scrollY; this.windowTop = window.top.scrollY;
@@ -131,9 +145,15 @@ export default {
this.windowTop = 133; this.windowTop = 133;
} }
}, },
/**
* Get URL of monitor
* @param {number} id ID of monitor
* @returns {string} Relative URL of monitor
*/
monitorURL(id) { monitorURL(id) {
return getMonitorRelativeURL(id); return getMonitorRelativeURL(id);
}, },
/** Clear the search bar */
clearSearchText() { clearSearchText() {
this.searchText = ""; this.searchText = "";
} }

View File

@@ -125,11 +125,16 @@ export default {
}, },
methods: { methods: {
/** Show dialog to confirm deletion */
deleteConfirm() { deleteConfirm() {
this.modal.hide(); this.modal.hide();
this.$refs.confirmDelete.show(); this.$refs.confirmDelete.show();
}, },
/**
* Show settings for specified notification
* @param {number} notificationID ID of notification to show
*/
show(notificationID) { show(notificationID) {
if (notificationID) { if (notificationID) {
this.id = notificationID; this.id = notificationID;
@@ -152,6 +157,7 @@ export default {
this.modal.show(); this.modal.show();
}, },
/** Submit the form to the server */
submit() { submit() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => { this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => {
@@ -170,6 +176,7 @@ export default {
}); });
}, },
/** Test the notification endpoint */
test() { test() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("testNotification", this.notification, (res) => { this.$root.getSocket().emit("testNotification", this.notification, (res) => {
@@ -178,6 +185,7 @@ export default {
}); });
}, },
/** Delete the notification endpoint */
deleteNotification() { deleteNotification() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("deleteNotification", this.id, (res) => { this.$root.getSocket().emit("deleteNotification", this.id, (res) => {
@@ -190,6 +198,7 @@ export default {
}); });
}, },
/** /**
* Get a unique default name for the notification
* @param {keyof NotificationFormList} notificationKey * @param {keyof NotificationFormList} notificationKey
* @return {string} * @return {string}
*/ */

View File

@@ -35,6 +35,7 @@ Chart.register(LineController, BarController, LineElement, PointElement, TimeSca
export default { export default {
components: { LineChart }, components: { LineChart },
props: { props: {
/** ID of monitor */
monitorId: { monitorId: {
type: Number, type: Number,
required: true, required: true,

View File

@@ -130,11 +130,16 @@ export default {
}, },
methods: { methods: {
/** Show dialog to confirm deletion */
deleteConfirm() { deleteConfirm() {
this.modal.hide(); this.modal.hide();
this.$refs.confirmDelete.show(); this.$refs.confirmDelete.show();
}, },
/**
* Show settings for specified proxy
* @param {number} proxyID ID of proxy to show
*/
show(proxyID) { show(proxyID) {
if (proxyID) { if (proxyID) {
this.id = proxyID; this.id = proxyID;
@@ -163,6 +168,7 @@ export default {
this.modal.show(); this.modal.show();
}, },
/** Submit form data for saving */
submit() { submit() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => { this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => {
@@ -180,6 +186,7 @@ export default {
}); });
}, },
/** Delete this proxy */
deleteProxy() { deleteProxy() {
this.processing = true; this.processing = true;
this.$root.getSocket().emit("deleteProxy", this.id, (res) => { this.$root.getSocket().emit("deleteProxy", this.id, (res) => {

View File

@@ -72,10 +72,12 @@ export default {
Tag, Tag,
}, },
props: { props: {
/** Are we in edit mode? */
editMode: { editMode: {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
/** Should tags be shown? */
showTags: { showTags: {
type: Boolean, type: Boolean,
} }
@@ -94,10 +96,20 @@ export default {
}, },
methods: { methods: {
/**
* Remove the specified group
* @param {number} index Index of group to remove
*/
removeGroup(index) { removeGroup(index) {
this.$root.publicGroupList.splice(index, 1); this.$root.publicGroupList.splice(index, 1);
}, },
/**
* Remove a monitor from a group
* @param {number} groupIndex Index of group to remove monitor
* from
* @param {number} index Index of monitor to remove
*/
removeMonitor(groupIndex, index) { removeMonitor(groupIndex, index) {
this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1); this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1);
}, },

View File

@@ -5,6 +5,7 @@
<script> <script>
export default { export default {
props: { props: {
/** Current status of monitor */
status: { status: {
type: Number, type: Number,
default: 0, default: 0,

View File

@@ -20,14 +20,20 @@
<script> <script>
export default { export default {
props: { props: {
/** Object representing tag */
item: { item: {
type: Object, type: Object,
required: true, required: true,
}, },
/** Function to remove tag */
remove: { remove: {
type: Function, type: Function,
default: null, default: null,
}, },
/**
* Size of tag
* @values normal, small
*/
size: { size: {
type: String, type: String,
default: "normal", default: "normal",

View File

@@ -139,6 +139,7 @@ export default {
VueMultiselect, VueMultiselect,
}, },
props: { props: {
/** Array of tags to be pre-selected */
preSelectedTags: { preSelectedTags: {
type: Array, type: Array,
default: () => [], default: () => [],
@@ -241,9 +242,11 @@ export default {
this.getExistingTags(); this.getExistingTags();
}, },
methods: { methods: {
/** Show the add tag dialog */
showAddDialog() { showAddDialog() {
this.modal.show(); this.modal.show();
}, },
/** Get all existing tags */
getExistingTags() { getExistingTags() {
this.$root.getSocket().emit("getTags", (res) => { this.$root.getSocket().emit("getTags", (res) => {
if (res.ok) { if (res.ok) {
@@ -253,6 +256,10 @@ export default {
} }
}); });
}, },
/**
* Delete the specified tag
* @param {Object} tag Object representing tag to delete
*/
deleteTag(item) { deleteTag(item) {
if (item.new) { if (item.new) {
// Undo Adding a new Tag // Undo Adding a new Tag
@@ -262,6 +269,13 @@ export default {
this.deleteTags.push(item); this.deleteTags.push(item);
} }
}, },
/**
* Get colour of text inside the tag
* @param {Object} option The tag that needs to be displayed.
* Defaults to "white" unless the tag has no color, which will
* then return the body color (based on application theme)
* @returns string
*/
textColor(option) { textColor(option) {
if (option.color) { if (option.color) {
return "white"; return "white";
@@ -269,6 +283,7 @@ export default {
return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit"; return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit";
} }
}, },
/** Add a draft tag */
addDraftTag() { addDraftTag() {
console.log("Adding Draft Tag: ", this.newDraftTag); console.log("Adding Draft Tag: ", this.newDraftTag);
if (this.newDraftTag.select != null) { if (this.newDraftTag.select != null) {
@@ -296,6 +311,7 @@ export default {
} }
this.clearDraftTag(); this.clearDraftTag();
}, },
/** Remove a draft tag */
clearDraftTag() { clearDraftTag() {
this.newDraftTag = { this.newDraftTag = {
name: null, name: null,
@@ -307,26 +323,51 @@ export default {
}; };
this.modal.hide(); this.modal.hide();
}, },
/**
* Add a tag asynchronously
* @param {Object} newTag Object representing new tag to add
* @returns {Promise<void>}
*/
addTagAsync(newTag) { addTagAsync(newTag) {
return new Promise((resolve) => { return new Promise((resolve) => {
this.$root.getSocket().emit("addTag", newTag, resolve); this.$root.getSocket().emit("addTag", newTag, resolve);
}); });
}, },
/**
* Add a tag to a monitor asynchronously
* @param {number} tagId ID of tag to add
* @param {number} monitorId ID of monitor to add tag to
* @param {string} value Value of tag
* @returns {Promise<void>}
*/
addMonitorTagAsync(tagId, monitorId, value) { addMonitorTagAsync(tagId, monitorId, value) {
return new Promise((resolve) => { return new Promise((resolve) => {
this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve); this.$root.getSocket().emit("addMonitorTag", tagId, monitorId, value, resolve);
}); });
}, },
/**
* Delete a tag from a monitor asynchronously
* @param {number} tagId ID of tag to remove
* @param {number} monitorId ID of monitor to remove tag from
* @param {string} value Value of tag
* @returns {Promise<void>}
*/
deleteMonitorTagAsync(tagId, monitorId, value) { deleteMonitorTagAsync(tagId, monitorId, value) {
return new Promise((resolve) => { return new Promise((resolve) => {
this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve); this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve);
}); });
}, },
/** Handle pressing Enter key when inside the modal */
onEnter() { onEnter() {
if (!this.validateDraftTag.invalid) { if (!this.validateDraftTag.invalid) {
this.addDraftTag(); this.addDraftTag();
} }
}, },
/**
* Submit the form data
* @param {number} monitorId ID of monitor this change affects
* @returns {void}
*/
async submit(monitorId) { async submit(monitorId) {
console.log(`Submitting tag changes for monitor ${monitorId}...`); console.log(`Submitting tag changes for monitor ${monitorId}...`);
this.processing = true; this.processing = true;

View File

@@ -29,10 +29,12 @@
<script> <script>
export default { export default {
props: { props: {
/** Heading of the section */
heading: { heading: {
type: String, type: String,
default: "", default: "",
}, },
/** Should the section be open by default? */
defaultOpen: { defaultOpen: {
type: Boolean, type: Boolean,
default: false, default: false,

View File

@@ -100,18 +100,22 @@ export default {
this.getStatus(); this.getStatus();
}, },
methods: { methods: {
/** Show the dialog */
show() { show() {
this.modal.show(); this.modal.show();
}, },
/** Show dialog to confirm enabling 2FA */
confirmEnableTwoFA() { confirmEnableTwoFA() {
this.$refs.confirmEnableTwoFA.show(); this.$refs.confirmEnableTwoFA.show();
}, },
/** Show dialog to confirm disabling 2FA */
confirmDisableTwoFA() { confirmDisableTwoFA() {
this.$refs.confirmDisableTwoFA.show(); this.$refs.confirmDisableTwoFA.show();
}, },
/** Prepare 2FA configuration */
prepare2FA() { prepare2FA() {
this.processing = true; this.processing = true;
@@ -126,6 +130,7 @@ export default {
}); });
}, },
/** Save the current 2FA configuration */
save2FA() { save2FA() {
this.processing = true; this.processing = true;
@@ -143,6 +148,7 @@ export default {
}); });
}, },
/** Disable 2FA for this user */
disable2FA() { disable2FA() {
this.processing = true; this.processing = true;
@@ -160,6 +166,7 @@ export default {
}); });
}, },
/** Verify the token generated by the user */
verifyToken() { verifyToken() {
this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => { this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => {
if (res.ok) { if (res.ok) {
@@ -170,6 +177,7 @@ export default {
}); });
}, },
/** Get current status of 2FA */
getStatus() { getStatus() {
this.$root.getSocket().emit("twoFAStatus", (res) => { this.$root.getSocket().emit("twoFAStatus", (res) => {
if (res.ok) { if (res.ok) {

View File

@@ -5,14 +5,17 @@
<script> <script>
export default { export default {
props: { props: {
/** Monitor this represents */
monitor: { monitor: {
type: Object, type: Object,
default: null, default: null,
}, },
/** Type of monitor */
type: { type: {
type: String, type: String,
default: null, default: null,
}, },
/** Is this a pill? */
pill: { pill: {
type: Boolean, type: Boolean,
default: false, default: false,

View File

@@ -133,10 +133,15 @@ export default {
}, },
methods: { methods: {
/**
* Show the confimation dialog confirming the configuration
* be imported
*/
confirmImport() { confirmImport() {
this.$refs.confirmImport.show(); this.$refs.confirmImport.show();
}, },
/** Download a backup of the configuration */
downloadBackup() { downloadBackup() {
let time = dayjs().format("YYYY_MM_DD-hh_mm_ss"); let time = dayjs().format("YYYY_MM_DD-hh_mm_ss");
let fileName = `Uptime_Kuma_Backup_${time}.json`; let fileName = `Uptime_Kuma_Backup_${time}.json`;
@@ -157,6 +162,10 @@ export default {
downloadItem.click(); downloadItem.click();
}, },
/**
* Import the specified backup file
* @returns {?string}
*/
importBackup() { importBackup() {
this.processing = true; this.processing = true;
let uploadItem = document.getElementById("import-backend").files; let uploadItem = document.getElementById("import-backend").files;

View File

@@ -178,10 +178,12 @@ export default {
}, },
methods: { methods: {
/** Save the settings */
saveGeneral() { saveGeneral() {
localStorage.timezone = this.$root.userTimezone; localStorage.timezone = this.$root.userTimezone;
this.saveSettings(); this.saveSettings();
}, },
/** Get the base URL of the application */
autoGetPrimaryBaseURL() { autoGetPrimaryBaseURL() {
this.settings.primaryBaseURL = location.protocol + "//" + location.host; this.settings.primaryBaseURL = location.protocol + "//" + location.host;
}, },

View File

@@ -90,6 +90,7 @@ export default {
}, },
methods: { methods: {
/** Get the current size of the database */
loadDatabaseSize() { loadDatabaseSize() {
log.debug("monitorhistory", "load database size"); log.debug("monitorhistory", "load database size");
this.$root.getSocket().emit("getDatabaseSize", (res) => { this.$root.getSocket().emit("getDatabaseSize", (res) => {
@@ -102,6 +103,7 @@ export default {
}); });
}, },
/** Request that the database is shrunk */
shrinkDatabase() { shrinkDatabase() {
this.$root.getSocket().emit("shrinkDatabase", (res) => { this.$root.getSocket().emit("shrinkDatabase", (res) => {
if (res.ok) { if (res.ok) {
@@ -113,10 +115,12 @@ export default {
}); });
}, },
/** Show the dialog to confirm clearing stats */
confirmClearStatistics() { confirmClearStatistics() {
this.$refs.confirmClearStatistics.show(); this.$refs.confirmClearStatistics.show();
}, },
/** Send the request to clear stats */
clearStatistics() { clearStatistics() {
this.$root.clearStatistics((res) => { this.$root.clearStatistics((res) => {
if (res.ok) { if (res.ok) {

View File

@@ -20,10 +20,10 @@
</button> </button>
</div> </div>
<div class="my-4"> <div class="my-4 pt-4">
<h4>{{ $t("settingsCertificateExpiry") }}</h4> <h5 class="my-4 settings-subheading">{{ $t("settingsCertificateExpiry") }}</h5>
<p>{{ $t("certificationExpiryDescription") }}</p> <p>{{ $t("certificationExpiryDescription") }}</p>
<div class="mt-2 mb-4 ps-2 cert-exp-days col-12 col-xl-6"> <div class="mt-1 mb-3 ps-2 cert-exp-days col-12 col-xl-6">
<div v-for="day in settings.tlsExpiryNotifyDays" :key="day" class="d-flex align-items-center justify-content-between cert-exp-day-row py-2"> <div v-for="day in settings.tlsExpiryNotifyDays" :key="day" class="d-flex align-items-center justify-content-between cert-exp-day-row py-2">
<span>{{ day }} {{ $tc("day", day) }}</span> <span>{{ day }} {{ $tc("day", day) }}</span>
<button type="button" class="btn-rm-expiry btn btn-outline-danger ms-2 py-1" @click="removeExpiryNotifDay(day)"> <button type="button" class="btn-rm-expiry btn btn-outline-danger ms-2 py-1" @click="removeExpiryNotifDay(day)">

View File

@@ -120,14 +120,17 @@ export default {
this.$root.getSocket().emit(prefix + "leave"); this.$root.getSocket().emit(prefix + "leave");
}, },
methods: { methods: {
/** Start the Cloudflare tunnel */
start() { start() {
this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken); this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken);
}, },
/** Stop the Cloudflare tunnel */
stop() { stop() {
this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => { this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => {
this.$root.toastRes(res); this.$root.toastRes(res);
}); });
}, },
/** Remove the token for the Cloudflare tunnel */
removeToken() { removeToken() {
this.$root.getSocket().emit(prefix + "removeToken"); this.$root.getSocket().emit(prefix + "removeToken");
this.cloudflareTunnelToken = ""; this.cloudflareTunnelToken = "";

View File

@@ -8,7 +8,7 @@
<button v-if="! settings.disableAuth" id="logout-btn" class="btn btn-danger ms-4 me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button> <button v-if="! settings.disableAuth" id="logout-btn" class="btn btn-danger ms-4 me-2 mb-2" @click="$root.logout">{{ $t("Logout") }}</button>
</p> </p>
<h5 class="my-4">{{ $t("Change Password") }}</h5> <h5 class="my-4 settings-subheading">{{ $t("Change Password") }}</h5>
<form class="mb-3" @submit.prevent="savePassword"> <form class="mb-3" @submit.prevent="savePassword">
<div class="mb-3"> <div class="mb-3">
<label for="current-password" class="form-label"> <label for="current-password" class="form-label">
@@ -62,7 +62,7 @@
</template> </template>
<div v-if="! settings.disableAuth" class="mt-5 mb-3"> <div v-if="! settings.disableAuth" class="mt-5 mb-3">
<h5 class="my-4"> <h5 class="my-4 settings-subheading">
{{ $t("Two Factor Authentication") }} {{ $t("Two Factor Authentication") }}
</h5> </h5>
<div class="mb-4"> <div class="mb-4">
@@ -78,7 +78,7 @@
<div class="my-4"> <div class="my-4">
<!-- Advanced --> <!-- Advanced -->
<h5 class="my-4">{{ $t("Advanced") }}</h5> <h5 class="my-4 settings-subheading">{{ $t("Advanced") }}</h5>
<div class="mb-4"> <div class="mb-4">
<button v-if="settings.disableAuth" id="enableAuth-btn" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button> <button v-if="settings.disableAuth" id="enableAuth-btn" class="btn btn-outline-primary me-2 mb-2" @click="enableAuth">{{ $t("Enable Auth") }}</button>
@@ -303,6 +303,7 @@ export default {
}, },
methods: { methods: {
/** Check new passwords match before saving them */
savePassword() { savePassword() {
if (this.password.newPassword !== this.password.repeatNewPassword) { if (this.password.newPassword !== this.password.repeatNewPassword) {
this.invalidPassword = true; this.invalidPassword = true;
@@ -320,6 +321,7 @@ export default {
} }
}, },
/** Disable authentication for web app access */
disableAuth() { disableAuth() {
this.settings.disableAuth = true; this.settings.disableAuth = true;
@@ -332,6 +334,7 @@ export default {
}, this.password.currentPassword); }, this.password.currentPassword);
}, },
/** Enable authentication for web app access */
enableAuth() { enableAuth() {
this.settings.disableAuth = false; this.settings.disableAuth = false;
this.saveSettings(); this.saveSettings();
@@ -346,15 +349,3 @@ export default {
}, },
}; };
</script> </script>
<style lang="scss" scoped>
@import "../../assets/vars.scss";
h5::after {
content: "";
display: block;
width: 50%;
padding-top: 8px;
border-bottom: 1px solid $dark-border-color;
}
</style>

View File

@@ -421,6 +421,7 @@ export default {
Next: "Следващ", Next: "Следващ",
"The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.", "The slug is already taken. Please choose another slug.": "Този слъг вече се използва. Моля изберете друг.",
"No Proxy": "Без прокси", "No Proxy": "Без прокси",
Authentication: "Удостоверяване",
"HTTP Basic Auth": "HTTP основно удостоверяване", "HTTP Basic Auth": "HTTP основно удостоверяване",
"New Status Page": "Нова статус страница", "New Status Page": "Нова статус страница",
"Page Not Found": "Страницата не е открита", "Page Not Found": "Страницата не е открита",

View File

@@ -421,6 +421,7 @@ export default {
Next: "Weiter", Next: "Weiter",
"The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.", "The slug is already taken. Please choose another slug.": "Der Slug ist bereits in Verwendung. Bitte wähle einen anderen.",
"No Proxy": "Kein Proxy", "No Proxy": "Kein Proxy",
Authentication: "Authentifizierung",
"HTTP Basic Auth": "HTTP Basisauthentifizierung", "HTTP Basic Auth": "HTTP Basisauthentifizierung",
"New Status Page": "Neue Status-Seite", "New Status Page": "Neue Status-Seite",
"Page Not Found": "Seite nicht gefunden", "Page Not Found": "Seite nicht gefunden",

View File

@@ -438,6 +438,7 @@ export default {
Next: "Next", Next: "Next",
"The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.", "The slug is already taken. Please choose another slug.": "The slug is already taken. Please choose another slug.",
"No Proxy": "No Proxy", "No Proxy": "No Proxy",
Authentication: "Authentication",
"HTTP Basic Auth": "HTTP Basic Auth", "HTTP Basic Auth": "HTTP Basic Auth",
"New Status Page": "New Status Page", "New Status Page": "New Status Page",
"Page Not Found": "Page Not Found", "Page Not Found": "Page Not Found",

View File

@@ -438,6 +438,7 @@ export default {
Next: "다음", Next: "다음",
"The slug is already taken. Please choose another slug.": "이미 존재하는 주소에요. 다른 주소를 사용해 주세요.", "The slug is already taken. Please choose another slug.": "이미 존재하는 주소에요. 다른 주소를 사용해 주세요.",
"No Proxy": "프록시 없음", "No Proxy": "프록시 없음",
Authentication: "인증",
"HTTP Basic Auth": "HTTP 인증", "HTTP Basic Auth": "HTTP 인증",
"New Status Page": "새로운 상태 페이지", "New Status Page": "새로운 상태 페이지",
"Page Not Found": "페이지를 찾을 수 없어요", "Page Not Found": "페이지를 찾을 수 없어요",

View File

@@ -428,6 +428,7 @@ export default {
Next: "Dalej", Next: "Dalej",
"The slug is already taken. Please choose another slug.": "Ten symbol jest już zajęty. Proszę, wybierz inny.", "The slug is already taken. Please choose another slug.": "Ten symbol jest już zajęty. Proszę, wybierz inny.",
"No Proxy": "Bez proxy", "No Proxy": "Bez proxy",
Authentication: "Uwierzytelnianie",
"HTTP Basic Auth": "Podstawowa autoryzacja HTTP", "HTTP Basic Auth": "Podstawowa autoryzacja HTTP",
"New Status Page": "Nowa strona statusu", "New Status Page": "Nowa strona statusu",
"Page Not Found": "Strona nie została znaleziona", "Page Not Found": "Strona nie została znaleziona",

View File

@@ -351,7 +351,8 @@ export default {
"Start or end with a-z 0-9 only": "Начало и окончание имени только на символы: a-z 0-9", "Start or end with a-z 0-9 only": "Начало и окончание имени только на символы: a-z 0-9",
"No consecutive dashes --": "Запрещено использовать тире --", "No consecutive dashes --": "Запрещено использовать тире --",
"HTTP Options": "HTTP Опции", "HTTP Options": "HTTP Опции",
"Basic Auth": "HTTP Авторизация", Authentication: "Аутентификация",
"HTTP Basic Auth": "HTTP Авторизация",
PushByTechulus: "Push by Techulus", PushByTechulus: "Push by Techulus",
clicksendsms: "ClickSend SMS", clicksendsms: "ClickSend SMS",
GoogleChat: "Google Chat (только Google Workspace)", GoogleChat: "Google Chat (только Google Workspace)",

View File

@@ -350,7 +350,8 @@ export default {
"Start or end with a-z 0-9 only": "Початок та закінчення імені лише на символи: a-z 0-9", "Start or end with a-z 0-9 only": "Початок та закінчення імені лише на символи: a-z 0-9",
"No consecutive dashes --": "Заборонено використовувати тире --", "No consecutive dashes --": "Заборонено використовувати тире --",
"HTTP Options": "HTTP Опції", "HTTP Options": "HTTP Опції",
"Basic Auth": "HTTP Авторизація", Authentication: "Аутентифікація",
"HTTP Basic Auth": "HTTP Авторизація",
PushByTechulus: "Push by Techulus", PushByTechulus: "Push by Techulus",
clicksendsms: "ClickSend SMS", clicksendsms: "ClickSend SMS",
GoogleChat: "Google Chat (тільки Google Workspace)", GoogleChat: "Google Chat (тільки Google Workspace)",

View File

@@ -436,6 +436,7 @@ export default {
Next: "下一步", Next: "下一步",
"The slug is already taken. Please choose another slug.": "该路径已被使用。请选择其他路径。", "The slug is already taken. Please choose another slug.": "该路径已被使用。请选择其他路径。",
"No Proxy": "无代理", "No Proxy": "无代理",
Authentication: "验证",
"HTTP Basic Auth": "HTTP 基础身份验证", "HTTP Basic Auth": "HTTP 基础身份验证",
"New Status Page": "新的状态页", "New Status Page": "新的状态页",
"Page Not Found": "未找到该页面", "Page Not Found": "未找到该页面",

View File

@@ -428,6 +428,7 @@ export default {
Next: "下一步", Next: "下一步",
"The slug is already taken. Please choose another slug.": "此 slug 已被使用。請選擇其他 slug。", "The slug is already taken. Please choose another slug.": "此 slug 已被使用。請選擇其他 slug。",
"No Proxy": "無 Proxy", "No Proxy": "無 Proxy",
Authentication: "驗證",
"HTTP Basic Auth": "HTTP 基本驗證", "HTTP Basic Auth": "HTTP 基本驗證",
"New Status Page": "新狀態頁", "New Status Page": "新狀態頁",
"Page Not Found": "找不到頁面", "Page Not Found": "找不到頁面",

View File

@@ -18,14 +18,31 @@ export default {
}, },
methods: { methods: {
/**
* Return a given value in the format YYYY-MM-DD HH:mm:ss
* @param {any} value Value to format as date time
* @returns {string}
*/
datetime(value) { datetime(value) {
return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss"); return this.datetimeFormat(value, "YYYY-MM-DD HH:mm:ss");
}, },
/**
* Return a given value in the format YYYY-MM-DD
* @param {any} value Value to format as date
* @returns {string}
*/
date(value) { date(value) {
return this.datetimeFormat(value, "YYYY-MM-DD"); return this.datetimeFormat(value, "YYYY-MM-DD");
}, },
/**
* Return a given value in the format HH:mm or if second is set
* to true, HH:mm:ss
* @param {any} value Value to format
* @param {boolean} second Should seconds be included?
* @returns {string}
*/
time(value, second = true) { time(value, second = true) {
let secondString; let secondString;
if (second) { if (second) {
@@ -36,6 +53,12 @@ export default {
return this.datetimeFormat(value, "HH:mm" + secondString); return this.datetimeFormat(value, "HH:mm" + secondString);
}, },
/**
* Return a value in a custom format
* @param {any} value Value to format
* @param {any} format Format to return value in
* @returns {string}
*/
datetimeFormat(value, format) { datetimeFormat(value, format) {
if (value !== undefined && value !== "") { if (value !== undefined && value !== "") {
return dayjs.utc(value).tz(this.timezone).format(format); return dayjs.utc(value).tz(this.timezone).format(format);

View File

@@ -22,6 +22,7 @@ export default {
}, },
methods: { methods: {
/** Change the application language */
async changeLang(lang) { async changeLang(lang) {
let message = (await langModules["../languages/" + lang + ".js"]()).default; let message = (await langModules["../languages/" + lang + ".js"]()).default;
this.$i18n.setLocaleMessage(lang, message); this.$i18n.setLocaleMessage(lang, message);

View File

@@ -12,11 +12,13 @@ export default {
}, },
methods: { methods: {
/** Handle screen resize */
onResize() { onResize() {
this.windowWidth = window.innerWidth; this.windowWidth = window.innerWidth;
this.updateBody(); this.updateBody();
}, },
/** Add css-class "mobile" to body if needed */
updateBody() { updateBody() {
if (this.isMobile) { if (this.isMobile) {
document.body.classList.add("mobile"); document.body.classList.add("mobile");

View File

@@ -62,6 +62,12 @@ export default {
methods: { methods: {
/**
* Initialize connection to socket server
* @param {boolean} [bypass = false] Should the check for if we
* are on a status page be bypassed?
* @returns {(void|null)}
*/
initSocketIO(bypass = false) { initSocketIO(bypass = false) {
// No need to re-init // No need to re-init
if (this.socket.initedSocketIO) { if (this.socket.initedSocketIO) {
@@ -258,10 +264,18 @@ export default {
socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res); socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res);
}, },
/**
* The storage currently in use
* @returns {Storage}
*/
storage() { storage() {
return (this.remember) ? localStorage : sessionStorage; return (this.remember) ? localStorage : sessionStorage;
}, },
/**
* Get payload of JWT cookie
* @returns {(Object|undefined)}
*/
getJWTPayload() { getJWTPayload() {
const jwtToken = this.$root.storage().token; const jwtToken = this.$root.storage().token;
@@ -271,10 +285,18 @@ export default {
return undefined; return undefined;
}, },
/**
* Get current socket
* @returns {Socket}
*/
getSocket() { getSocket() {
return socket; return socket;
}, },
/**
* Show success or error toast dependant on response status code
* @param {Object} res Response object
*/
toastRes(res) { toastRes(res) {
if (res.ok) { if (res.ok) {
toast.success(res.msg); toast.success(res.msg);
@@ -283,14 +305,35 @@ export default {
} }
}, },
/**
* Show a success toast
* @param {string} msg Message to show
*/
toastSuccess(msg) { toastSuccess(msg) {
toast.success(msg); toast.success(msg);
}, },
/**
* Show an error toast
* @param {string} msg Message to show
*/
toastError(msg) { toastError(msg) {
toast.error(msg); toast.error(msg);
}, },
/**
* Callback for login
* @callback loginCB
* @param {Object} res Response object
*/
/**
* Send request to log user in
* @param {string} username Username to log in with
* @param {string} password Password to log in with
* @param {string} token User token
* @param {loginCB} callback Callback to call with result
*/
login(username, password, token, callback) { login(username, password, token, callback) {
socket.emit("login", { socket.emit("login", {
username, username,
@@ -315,6 +358,10 @@ export default {
}); });
}, },
/**
* Log in using a token
* @param {string} token Token to log in with
*/
loginByToken(token) { loginByToken(token) {
socket.emit("loginByToken", token, (res) => { socket.emit("loginByToken", token, (res) => {
this.allowLoginDialog = true; this.allowLoginDialog = true;
@@ -328,6 +375,7 @@ export default {
}); });
}, },
/** Log out of the web application */
logout() { logout() {
socket.emit("logout", () => { }); socket.emit("logout", () => { });
this.storage().removeItem("token"); this.storage().removeItem("token");
@@ -337,26 +385,54 @@ export default {
this.clearData(); this.clearData();
}, },
/**
* Callback for general socket requests
* @callback socketCB
* @param {Object} res Result of operation
*/
/** Prepare 2FA configuration */
prepare2FA(callback) { prepare2FA(callback) {
socket.emit("prepare2FA", callback); socket.emit("prepare2FA", callback);
}, },
/**
* Save the current 2FA configuration
* @param {any} secret Unused
* @param {socketCB} callback
*/
save2FA(secret, callback) { save2FA(secret, callback) {
socket.emit("save2FA", callback); socket.emit("save2FA", callback);
}, },
/**
* Disable 2FA for this user
* @param {socketCB} callback
*/
disable2FA(callback) { disable2FA(callback) {
socket.emit("disable2FA", callback); socket.emit("disable2FA", callback);
}, },
/**
* Verify the provided 2FA token
* @param {string} token Token to verify
* @param {socketCB} callback
*/
verifyToken(token, callback) { verifyToken(token, callback) {
socket.emit("verifyToken", token, callback); socket.emit("verifyToken", token, callback);
}, },
/**
* Get current 2FA status
* @param {socketCB} callback
*/
twoFAStatus(callback) { twoFAStatus(callback) {
socket.emit("twoFAStatus", callback); socket.emit("twoFAStatus", callback);
}, },
/**
* Get list of monitors
* @param {socketCB} callback
*/
getMonitorList(callback) { getMonitorList(callback) {
if (! callback) { if (! callback) {
callback = () => { }; callback = () => { };
@@ -364,36 +440,74 @@ export default {
socket.emit("getMonitorList", callback); socket.emit("getMonitorList", callback);
}, },
/**
* Add a monitor
* @param {Object} monitor Object representing monitor to add
* @param {socketCB} callback
*/
add(monitor, callback) { add(monitor, callback) {
socket.emit("add", monitor, callback); socket.emit("add", monitor, callback);
}, },
/**
* Delete monitor by ID
* @param {number} monitorID ID of monitor to delete
* @param {socketCB} callback
*/
deleteMonitor(monitorID, callback) { deleteMonitor(monitorID, callback) {
socket.emit("deleteMonitor", monitorID, callback); socket.emit("deleteMonitor", monitorID, callback);
}, },
/** Clear the hearbeat list */
clearData() { clearData() {
console.log("reset heartbeat list"); console.log("reset heartbeat list");
this.heartbeatList = {}; this.heartbeatList = {};
this.importantHeartbeatList = {}; this.importantHeartbeatList = {};
}, },
/**
* Upload the provided backup
* @param {string} uploadedJSON JSON to upload
* @param {string} importHandle Type of import. If set to
* most data in database will be replaced
* @param {socketCB} callback
*/
uploadBackup(uploadedJSON, importHandle, callback) { uploadBackup(uploadedJSON, importHandle, callback) {
socket.emit("uploadBackup", uploadedJSON, importHandle, callback); socket.emit("uploadBackup", uploadedJSON, importHandle, callback);
}, },
/**
* Clear events for a specified monitor
* @param {number} monitorID ID of monitor to clear
* @param {socketCB} callback
*/
clearEvents(monitorID, callback) { clearEvents(monitorID, callback) {
socket.emit("clearEvents", monitorID, callback); socket.emit("clearEvents", monitorID, callback);
}, },
/**
* Clear the heartbeats of a specified monitor
* @param {number} monitorID Id of monitor to clear
* @param {socketCB} callback
*/
clearHeartbeats(monitorID, callback) { clearHeartbeats(monitorID, callback) {
socket.emit("clearHeartbeats", monitorID, callback); socket.emit("clearHeartbeats", monitorID, callback);
}, },
/**
* Clear all statistics
* @param {socketCB} callback
*/
clearStatistics(callback) { clearStatistics(callback) {
socket.emit("clearStatistics", callback); socket.emit("clearStatistics", callback);
}, },
/**
* Get monitor beats for a specific monitor in a time range
* @param {number} monitorID ID of monitor to fetch
* @param {number} period Time in hours from now
* @param {socketCB} callback
*/
getMonitorBeats(monitorID, period, callback) { getMonitorBeats(monitorID, period, callback) {
socket.emit("getMonitorBeats", monitorID, period, callback); socket.emit("getMonitorBeats", monitorID, period, callback);
} }

View File

@@ -75,6 +75,7 @@ export default {
}, },
methods: { methods: {
/** Update the theme color meta tag */
updateThemeColorMeta() { updateThemeColorMeta() {
if (this.theme === "dark") { if (this.theme === "dark") {
document.querySelector("#theme-color").setAttribute("content", "#161B22"); document.querySelector("#theme-color").setAttribute("content", "#161B22");

View File

@@ -51,6 +51,7 @@ export default {
}; };
}, },
methods: { methods: {
/** Submit form data to add new status page */
async submit() { async submit() {
this.processing = true; this.processing = true;

View File

@@ -289,39 +289,47 @@ export default {
}, },
methods: { methods: {
/** Request a test notification be sent for this monitor */
testNotification() { testNotification() {
this.$root.getSocket().emit("testNotification", this.monitor.id); this.$root.getSocket().emit("testNotification", this.monitor.id);
toast.success("Test notification is requested."); toast.success("Test notification is requested.");
}, },
/** Show dialog to confirm pause */
pauseDialog() { pauseDialog() {
this.$refs.confirmPause.show(); this.$refs.confirmPause.show();
}, },
/** Resume this monitor */
resumeMonitor() { resumeMonitor() {
this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => { this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => {
this.$root.toastRes(res); this.$root.toastRes(res);
}); });
}, },
/** Request that this monitor is paused */
pauseMonitor() { pauseMonitor() {
this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => { this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => {
this.$root.toastRes(res); this.$root.toastRes(res);
}); });
}, },
/** Show dialog to confirm deletion */
deleteDialog() { deleteDialog() {
this.$refs.confirmDelete.show(); this.$refs.confirmDelete.show();
}, },
/** Show dialog to confirm clearing events */
clearEventsDialog() { clearEventsDialog() {
this.$refs.confirmClearEvents.show(); this.$refs.confirmClearEvents.show();
}, },
/** Show dialog to confirm clearing heartbeats */
clearHeartbeatsDialog() { clearHeartbeatsDialog() {
this.$refs.confirmClearHeartbeats.show(); this.$refs.confirmClearHeartbeats.show();
}, },
/** Request that this monitor is deleted */
deleteMonitor() { deleteMonitor() {
this.$root.deleteMonitor(this.monitor.id, (res) => { this.$root.deleteMonitor(this.monitor.id, (res) => {
if (res.ok) { if (res.ok) {
@@ -333,6 +341,7 @@ export default {
}); });
}, },
/** Request that this monitors events are cleared */
clearEvents() { clearEvents() {
this.$root.clearEvents(this.monitor.id, (res) => { this.$root.clearEvents(this.monitor.id, (res) => {
if (! res.ok) { if (! res.ok) {
@@ -341,6 +350,7 @@ export default {
}); });
}, },
/** Request that this monitors heartbeats are cleared */
clearHeartbeats() { clearHeartbeats() {
this.$root.clearHeartbeats(this.monitor.id, (res) => { this.$root.clearHeartbeats(this.monitor.id, (res) => {
if (! res.ok) { if (! res.ok) {
@@ -349,6 +359,11 @@ export default {
}); });
}, },
/**
* Return the correct title for the ping stat
* @param {boolean} [average=false] Is the statistic an average?
* @returns {string} Title formated dependant on monitor type
*/
pingTitle(average = false) { pingTitle(average = false) {
let translationPrefix = ""; let translationPrefix = "";
if (average) { if (average) {

View File

@@ -369,17 +369,17 @@
</div> </div>
<!-- HTTP Auth --> <!-- HTTP Auth -->
<h4 class="mt-5 mb-2">{{ $t("HTTP Authentication") }}</h4> <h4 class="mt-5 mb-2">{{ $t("Authentication") }}</h4>
<!-- Method --> <!-- Method -->
<div class="my-3"> <div class="my-3">
<label for="method" class="form-label">{{ $t("Method") }}</label> <label for="method" class="form-label">{{ $t("Method") }}</label>
<select id="method" v-model="monitor.authMethod" class="form-select"> <select id="method" v-model="monitor.authMethod" class="form-select">
<option :value="null"> <option :value="null">
None {{ $t("None") }}
</option> </option>
<option value="basic"> <option value="basic">
Basic {{ $t("HTTP Basic Auth") }}
</option> </option>
<option value="ntlm"> <option value="ntlm">
NTLM NTLM
@@ -404,7 +404,7 @@
<div class="my-3"> <div class="my-3">
<label for="basicauth" class="form-label">{{ $t("Workstation") }}</label> <label for="basicauth" class="form-label">{{ $t("Workstation") }}</label>
<input id="basicauth-workstation" v-model="monitor.authWorkstation" type="password" autocomplete="new-password" class="form-control" :placeholder="$t('Workstation')"> <input id="basicauth-workstation" v-model="monitor.authWorkstation" type="text" class="form-control" :placeholder="$t('Workstation')">
</div> </div>
</template> </template>
</template> </template>
@@ -573,6 +573,7 @@ export default {
this.dnsresolvetypeOptions = dnsresolvetypeOptions; this.dnsresolvetypeOptions = dnsresolvetypeOptions;
}, },
methods: { methods: {
/** Initialize the edit monitor form */
init() { init() {
if (this.isAdd) { if (this.isAdd) {
@@ -583,6 +584,7 @@ export default {
method: "GET", method: "GET",
interval: 60, interval: 60,
retryInterval: this.interval, retryInterval: this.interval,
databaseConnectionString: "Server=<hostname>,<port>;Database=<your database>;User Id=<your user id>;Password=<your password>;Encrypt=<true/false>;TrustServerCertificate=<Yes/No>;Connection Timeout=<int>",
maxretries: 0, maxretries: 0,
notificationIDList: {}, notificationIDList: {},
ignoreTls: false, ignoreTls: false,
@@ -630,6 +632,10 @@ export default {
}, },
/**
* Validate form input
* @returns {boolean} Is the form input valid?
*/
isInputValid() { isInputValid() {
if (this.monitor.body) { if (this.monitor.body) {
try { try {
@@ -650,6 +656,10 @@ export default {
return true; return true;
}, },
/**
* Submit the form data for processing
* @returns {void}
*/
async submit() { async submit() {
this.processing = true; this.processing = true;
@@ -694,14 +704,20 @@ export default {
} }
}, },
// Added a Notification Event /**
// Enable it if the notification is added in EditMonitor.vue * Added a Notification Event
* Enable it if the notification is added in EditMonitor.vue
* @param {number} id ID of notification to add
*/
addedNotification(id) { addedNotification(id) {
this.monitor.notificationIDList[id] = true; this.monitor.notificationIDList[id] = true;
}, },
// Added a Proxy Event /**
// Enable it if the proxy is added in EditMonitor.vue * Added a Proxy Event
* Enable it if the proxy is added in EditMonitor.vue
* @param {number} id ID of proxy to add
*/
addedProxy(id) { addedProxy(id) {
this.monitor.proxyId = id; this.monitor.proxyId = id;
}, },

View File

@@ -1,6 +1,6 @@
<template> <template>
<transition name="slide-fade" appear> <transition name="slide-fade" appear>
<MonitorList /> <MonitorList :scrollbar="true" />
</transition> </transition>
</template> </template>
@@ -14,3 +14,11 @@ export default {
}; };
</script> </script>
<style lang="scss" scoped>
@import "../assets/vars";
.shadow-box {
padding: 20px;
}
</style>

View File

@@ -51,6 +51,11 @@ export default {
}, },
methods: { methods: {
/**
* Get the correct URL for the icon
* @param {string} icon Path for icon
* @returns {string} Correctly formatted path including port numbers
*/
icon(icon) { icon(icon) {
if (icon === "/icon.svg") { if (icon === "/icon.svg") {
return icon; return icon;

View File

@@ -45,6 +45,7 @@ export default {
}, },
methods: { methods: {
/** Go back 1 in browser history */
goBack() { goBack() {
history.back(); history.back();
} }

View File

@@ -118,13 +118,17 @@ export default {
methods: { methods: {
// For desktop only, mobile do nothing /**
* Load the general settings page
* For desktop only, on mobile do nothing
*/
loadGeneralPage() { loadGeneralPage() {
if (!this.currentPage && !this.$root.isMobile) { if (!this.currentPage && !this.$root.isMobile) {
this.$router.push("/settings/general"); this.$router.push("/settings/general");
} }
}, },
/** Load settings from server */
loadSettings() { loadSettings() {
this.$root.getSocket().emit("getSettings", (res) => { this.$root.getSocket().emit("getSettings", (res) => {
this.settings = res.data; this.settings = res.data;
@@ -146,16 +150,23 @@ export default {
} }
if (this.settings.tlsExpiryNotifyDays === undefined) { if (this.settings.tlsExpiryNotifyDays === undefined) {
this.settings.tlsExpiryNotifyDays = []; this.settings.tlsExpiryNotifyDays = [ 7, 14, 21 ];
} }
this.settingsLoaded = true; this.settingsLoaded = true;
}); });
}, },
/**
* Callback for saving settings
* @callback saveSettingsCB
* @param {Object} res Result of operation
*/
/** /**
* Save Settings * Save Settings
* @param currentPassword (Optional) Only need for disableAuth to true * @param {saveSettingsCB} [callback]
* @param {string} [currentPassword] Only need for disableAuth to true
*/ */
saveSettings(callback, currentPassword) { saveSettings(callback, currentPassword) {
this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => { this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => {

View File

@@ -71,6 +71,10 @@ export default {
}); });
}, },
methods: { methods: {
/**
* Submit form data for processing
* @returns {void}
*/
submit() { submit() {
this.processing = true; this.processing = true;

View File

@@ -332,6 +332,7 @@ export default {
}, },
props: { props: {
/** Override for the status page slug */
overrideSlug: { overrideSlug: {
type: String, type: String,
required: false, required: false,
@@ -587,10 +588,16 @@ export default {
} }
}, },
/**
* Provide syntax highlighting for CSS
* @param {string} code Text to highlight
* @returns {string}
*/
highlighter(code) { highlighter(code) {
return highlight(code, languages.css); return highlight(code, languages.css);
}, },
/** Update the heartbeat list and update favicon if neccessary */
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) {
@@ -619,6 +626,7 @@ export default {
} }
}, },
/** Enable editing mode */
edit() { edit() {
if (this.hasToken) { if (this.hasToken) {
this.$root.initSocketIO(true); this.$root.initSocketIO(true);
@@ -630,6 +638,7 @@ export default {
} }
}, },
/** Save the status page */
save() { save() {
let startTime = new Date(); let startTime = new Date();
this.config.slug = this.config.slug.trim().toLowerCase(); this.config.slug = this.config.slug.trim().toLowerCase();
@@ -657,10 +666,12 @@ export default {
}); });
}, },
/** Show dialog confirming deletion */
deleteDialog() { deleteDialog() {
this.$refs.confirmDelete.show(); this.$refs.confirmDelete.show();
}, },
/** Request deletion of this status page */
deleteStatusPage() { deleteStatusPage() {
this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => { this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => {
if (res.ok) { if (res.ok) {
@@ -672,10 +683,16 @@ export default {
}); });
}, },
/**
* Returns label for a specifed monitor
* @param {Object} monitor Object representing monitor
* @returns {string}
*/
monitorSelectorLabel(monitor) { monitorSelectorLabel(monitor) {
return `${monitor.name}`; return `${monitor.name}`;
}, },
/** Add a group to the status page */
addGroup() { addGroup() {
let groupName = this.$t("Untitled Group"); let groupName = this.$t("Untitled Group");
@@ -689,27 +706,32 @@ export default {
}); });
}, },
/** Add a domain to the status page */
addDomainField() { addDomainField() {
this.config.domainNameList.push(""); this.config.domainNameList.push("");
}, },
/** Discard changes to status page */
discard() { discard() {
location.href = "/status/" + this.slug; location.href = "/status/" + this.slug;
}, },
/** /**
* Crop Success * Set URL of new image after successful crop operation
* @param {string} imgDataUrl URL of image in data:// format
*/ */
cropSuccess(imgDataUrl) { cropSuccess(imgDataUrl) {
this.imgDataUrl = imgDataUrl; this.imgDataUrl = imgDataUrl;
}, },
/** Show image crop dialog if in edit mode */
showImageCropUploadMethod() { showImageCropUploadMethod() {
if (this.editMode) { if (this.editMode) {
this.showImageCropUpload = true; this.showImageCropUpload = true;
} }
}, },
/** Create an incident for this status page */
createIncident() { createIncident() {
this.enableEditIncidentMode = true; this.enableEditIncidentMode = true;
@@ -724,6 +746,7 @@ export default {
}; };
}, },
/** Post the incident to the status page */
postIncident() { postIncident() {
if (this.incident.title === "" || this.incident.content === "") { if (this.incident.title === "" || this.incident.content === "") {
toast.error(this.$t("Please input title and content")); toast.error(this.$t("Please input title and content"));
@@ -743,14 +766,13 @@ export default {
}, },
/** /** Click Edit Button */
* Click Edit Button
*/
editIncident() { editIncident() {
this.enableEditIncidentMode = true; this.enableEditIncidentMode = true;
this.previousIncident = Object.assign({}, this.incident); this.previousIncident = Object.assign({}, this.incident);
}, },
/** Cancel creation or editing of incident */
cancelIncident() { cancelIncident() {
this.enableEditIncidentMode = false; this.enableEditIncidentMode = false;
@@ -760,16 +782,25 @@ export default {
} }
}, },
/** Unpin the incident */
unpinIncident() { unpinIncident() {
this.$root.getSocket().emit("unpinIncident", this.slug, () => { this.$root.getSocket().emit("unpinIncident", this.slug, () => {
this.incident = null; this.incident = null;
}); });
}, },
/**
* Get the relative time difference of a date from now
* @returns {string}
*/
dateFromNow(date) { dateFromNow(date) {
return dayjs.utc(date).fromNow(); return dayjs.utc(date).fromNow();
}, },
/**
* Remove a domain from the status page
* @param {number} index Index of domain to remove
*/
removeDomain(index) { removeDomain(index) {
this.config.domainNameList.splice(index, 1); this.config.domainNameList.splice(index, 1);
}, },

View File

@@ -65,12 +65,12 @@ const routes = [
path: "/add", path: "/add",
component: EditMonitor, component: EditMonitor,
}, },
{
path: "/list",
component: List,
},
], ],
}, },
{
path: "/list",
component: List,
},
{ {
path: "/settings", path: "/settings",
component: Settings, component: Settings,

View File

@@ -26,8 +26,8 @@ function getTimezoneOffset(timeZone) {
/** /**
* Returns a list of timezones sorted by their offset from UTC. * Returns a list of timezones sorted by their offset from UTC.
* @param {Array} timezones - An array of timezone objects. * @param {Object[]} timezones An array of timezone objects.
* @returns {Array} A list of the given timezones sorted by their offset from UTC. * @returns {Object[]} A list of the given timezones sorted by their offset from UTC.
* *
* Generated by Trelent * Generated by Trelent
*/ */
@@ -63,6 +63,7 @@ export function timezoneList() {
return result; return result;
} }
/** Set the locale of the HTML page */
export function setPageLocale() { export function setPageLocale() {
const html = document.documentElement; const html = document.documentElement;
html.setAttribute("lang", currentLocale() ); html.setAttribute("lang", currentLocale() );
@@ -70,7 +71,9 @@ export function setPageLocale() {
} }
/** /**
* Get the base URL
* Mainly used for dev, because the backend and the frontend are in different ports. * Mainly used for dev, because the backend and the frontend are in different ports.
* @returns {string}
*/ */
export function getResBaseURL() { export function getResBaseURL() {
const env = process.env.NODE_ENV; const env = process.env.NODE_ENV;

View File

@@ -18,6 +18,7 @@ exports.PENDING = 2;
exports.STATUS_PAGE_ALL_DOWN = 0; exports.STATUS_PAGE_ALL_DOWN = 0;
exports.STATUS_PAGE_ALL_UP = 1; exports.STATUS_PAGE_ALL_UP = 1;
exports.STATUS_PAGE_PARTIAL_DOWN = 2; exports.STATUS_PAGE_PARTIAL_DOWN = 2;
/** Flip the status of s */
function flipStatus(s) { function flipStatus(s) {
if (s === exports.UP) { if (s === exports.UP) {
return exports.DOWN; return exports.DOWN;
@@ -28,6 +29,10 @@ function flipStatus(s) {
return s; return s;
} }
exports.flipStatus = flipStatus; exports.flipStatus = flipStatus;
/**
* Delays for specified number of seconds
* @param ms Number of milliseconds to sleep for
*/
function sleep(ms) { function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
@@ -83,6 +88,12 @@ class Logger {
this.debug("server", this.hideLog); this.debug("server", this.hideLog);
} }
} }
/**
* Write a message to the log
* @param module The module the log comes from
* @param msg Message to write
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
*/
log(module, msg, level) { log(module, msg, level) {
if (this.hideLog[level] && this.hideLog[level].includes(module)) { if (this.hideLog[level] && this.hideLog[level].includes(module)) {
return; return;
@@ -109,18 +120,44 @@ class Logger {
console.log(formattedMessage); console.log(formattedMessage);
} }
} }
/**
* Log an INFO message
* @param module Module log comes from
* @param msg Message to write
*/
info(module, msg) { info(module, msg) {
this.log(module, msg, "info"); this.log(module, msg, "info");
} }
/**
* Log a WARN message
* @param module Module log comes from
* @param msg Message to write
*/
warn(module, msg) { warn(module, msg) {
this.log(module, msg, "warn"); this.log(module, msg, "warn");
} }
/**
* Log an ERROR message
* @param module Module log comes from
* @param msg Message to write
*/
error(module, msg) { error(module, msg) {
this.log(module, msg, "error"); this.log(module, msg, "error");
} }
/**
* Log a DEBUG message
* @param module Module log comes from
* @param msg Message to write
*/
debug(module, msg) { debug(module, msg) {
this.log(module, msg, "debug"); this.log(module, msg, "debug");
} }
/**
* Log an exeption as an ERROR
* @param module Module log comes from
* @param exception The exeption to include
* @param msg The message to write
*/
exception(module, exception, msg) { exception(module, exception, msg) {
let finalMessage = exception; let finalMessage = exception;
if (msg) { if (msg) {
@@ -130,13 +167,13 @@ class Logger {
} }
} }
exports.log = new Logger(); exports.log = new Logger();
/**
* String.prototype.replaceAll() polyfill
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
* @author Chris Ferdinandi
* @license MIT
*/
function polyfill() { function polyfill() {
/**
* String.prototype.replaceAll() polyfill
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
* @author Chris Ferdinandi
* @license MIT
*/
if (!String.prototype.replaceAll) { if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function (str, newStr) { String.prototype.replaceAll = function (str, newStr) {
// If a regex pattern // If a regex pattern
@@ -153,6 +190,10 @@ class TimeLogger {
constructor() { constructor() {
this.startTime = dayjs().valueOf(); this.startTime = dayjs().valueOf();
} }
/**
* Output time since start of monitor
* @param name Name of monitor
*/
print(name) { print(name) {
if (exports.isDev && process.env.TIMELOGGER === "1") { if (exports.isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms"); console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms");
@@ -201,6 +242,13 @@ let getRandomBytes = ((typeof window !== 'undefined' && window.crypto)
: function () { : function () {
return require("crypto").randomBytes; return require("crypto").randomBytes;
})(); })();
/**
* Get a random integer suitable for use in cryptography between upper
* and lower bounds.
* @param min Minimum value of integer
* @param max Maximum value of integer
* @returns Cryptographically suitable random integer
*/
function getCryptoRandomInt(min, max) { function getCryptoRandomInt(min, max) {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng // synchronous version of: https://github.com/joepie91/node-random-number-csprng
const range = max - min; const range = max - min;
@@ -231,6 +279,11 @@ function getCryptoRandomInt(min, max) {
} }
} }
exports.getCryptoRandomInt = getCryptoRandomInt; exports.getCryptoRandomInt = getCryptoRandomInt;
/**
* Generate a secret
* @param length Lenght of secret to generate
* @returns
*/
function genSecret(length = 64) { function genSecret(length = 64) {
let secret = ""; let secret = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -241,6 +294,11 @@ function genSecret(length = 64) {
return secret; return secret;
} }
exports.genSecret = genSecret; exports.genSecret = genSecret;
/**
* Get the path of a monitor
* @param id ID of monitor
* @returns Formatted relative path
*/
function getMonitorRelativeURL(id) { function getMonitorRelativeURL(id) {
return "/dashboard/" + id; return "/dashboard/" + id;
} }

View File

@@ -19,7 +19,7 @@ export const STATUS_PAGE_ALL_DOWN = 0;
export const STATUS_PAGE_ALL_UP = 1; export const STATUS_PAGE_ALL_UP = 1;
export const STATUS_PAGE_PARTIAL_DOWN = 2; export const STATUS_PAGE_PARTIAL_DOWN = 2;
/** Flip the status of s */
export function flipStatus(s: number) { export function flipStatus(s: number) {
if (s === UP) { if (s === UP) {
return DOWN; return DOWN;
@@ -32,6 +32,10 @@ export function flipStatus(s: number) {
return s; return s;
} }
/**
* Delays for specified number of seconds
* @param ms Number of milliseconds to sleep for
*/
export function sleep(ms: number) { export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
@@ -94,6 +98,12 @@ class Logger {
} }
} }
/**
* Write a message to the log
* @param module The module the log comes from
* @param msg Message to write
* @param level Log level. One of INFO, WARN, ERROR, DEBUG or can be customized.
*/
log(module: string, msg: any, level: string) { log(module: string, msg: any, level: string) {
if (this.hideLog[level] && this.hideLog[level].includes(module)) { if (this.hideLog[level] && this.hideLog[level].includes(module)) {
return; return;
@@ -120,22 +130,48 @@ class Logger {
} }
} }
/**
* Log an INFO message
* @param module Module log comes from
* @param msg Message to write
*/
info(module: string, msg: any) { info(module: string, msg: any) {
this.log(module, msg, "info"); this.log(module, msg, "info");
} }
/**
* Log a WARN message
* @param module Module log comes from
* @param msg Message to write
*/
warn(module: string, msg: any) { warn(module: string, msg: any) {
this.log(module, msg, "warn"); this.log(module, msg, "warn");
} }
error(module: string, msg: any) { /**
* Log an ERROR message
* @param module Module log comes from
* @param msg Message to write
*/
error(module: string, msg: any) {
this.log(module, msg, "error"); this.log(module, msg, "error");
} }
debug(module: string, msg: any) { /**
* Log a DEBUG message
* @param module Module log comes from
* @param msg Message to write
*/
debug(module: string, msg: any) {
this.log(module, msg, "debug"); this.log(module, msg, "debug");
} }
/**
* Log an exeption as an ERROR
* @param module Module log comes from
* @param exception The exeption to include
* @param msg The message to write
*/
exception(module: string, exception: any, msg: any) { exception(module: string, exception: any, msg: any) {
let finalMessage = exception let finalMessage = exception
@@ -151,13 +187,13 @@ export const log = new Logger();
declare global { interface String { replaceAll(str: string, newStr: string): string; } } declare global { interface String { replaceAll(str: string, newStr: string): string; } }
/**
* String.prototype.replaceAll() polyfill
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
* @author Chris Ferdinandi
* @license MIT
*/
export function polyfill() { export function polyfill() {
/**
* String.prototype.replaceAll() polyfill
* https://gomakethings.com/how-to-replace-a-section-of-a-string-with-another-one-with-vanilla-js/
* @author Chris Ferdinandi
* @license MIT
*/
if (!String.prototype.replaceAll) { if (!String.prototype.replaceAll) {
String.prototype.replaceAll = function (str: string, newStr: string) { String.prototype.replaceAll = function (str: string, newStr: string) {
// If a regex pattern // If a regex pattern
@@ -177,7 +213,10 @@ export class TimeLogger {
constructor() { constructor() {
this.startTime = dayjs().valueOf(); this.startTime = dayjs().valueOf();
} }
/**
* Output time since start of monitor
* @param name Name of monitor
*/
print(name: string) { print(name: string) {
if (isDev && process.env.TIMELOGGER === "1") { if (isDev && process.env.TIMELOGGER === "1") {
console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms") console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms")
@@ -231,6 +270,13 @@ let getRandomBytes = (
} }
)(); )();
/**
* Get a random integer suitable for use in cryptography between upper
* and lower bounds.
* @param min Minimum value of integer
* @param max Maximum value of integer
* @returns Cryptographically suitable random integer
*/
export function getCryptoRandomInt(min: number, max: number):number { export function getCryptoRandomInt(min: number, max: number):number {
// synchronous version of: https://github.com/joepie91/node-random-number-csprng // synchronous version of: https://github.com/joepie91/node-random-number-csprng
@@ -267,6 +313,11 @@ export function getCryptoRandomInt(min: number, max: number):number {
} }
} }
/**
* Generate a random alphanumeric string of fixed length
* @param length Length of string to generate
* @returns string
*/
export function genSecret(length = 64) { export function genSecret(length = 64) {
let secret = ""; let secret = "";
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
@@ -277,6 +328,11 @@ export function genSecret(length = 64) {
return secret; return secret;
} }
/**
* Get the path of a monitor
* @param id ID of monitor
* @returns Formatted relative path
*/
export function getMonitorRelativeURL(id: string) { export function getMonitorRelativeURL(id: string) {
return "/dashboard/" + id; return "/dashboard/" + id;
} }