From d939d03690b38ced23261b49cc010d76ef75d267 Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Wed, 1 Jun 2022 23:44:10 +0100 Subject: [PATCH 2/9] Added JSDoc for src/components/settings/* Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com> --- src/components/settings/Backup.vue | 9 +++++++++ src/components/settings/General.vue | 2 ++ src/components/settings/MonitorHistory.vue | 4 ++++ src/components/settings/ReverseProxy.vue | 3 +++ src/components/settings/Security.vue | 3 +++ 5 files changed, 21 insertions(+) diff --git a/src/components/settings/Backup.vue b/src/components/settings/Backup.vue index ba0f92f0e..685a4c6ba 100644 --- a/src/components/settings/Backup.vue +++ b/src/components/settings/Backup.vue @@ -133,10 +133,15 @@ export default { }, methods: { + /** + * Show the confimation dialog confirming the configuration + * be imported + */ confirmImport() { this.$refs.confirmImport.show(); }, + /** Download a backup of the configuration */ downloadBackup() { let time = dayjs().format("YYYY_MM_DD-hh_mm_ss"); let fileName = `Uptime_Kuma_Backup_${time}.json`; @@ -157,6 +162,10 @@ export default { downloadItem.click(); }, + /** + * Import the specified backup file + * @returns {?string} + */ importBackup() { this.processing = true; let uploadItem = document.getElementById("import-backend").files; diff --git a/src/components/settings/General.vue b/src/components/settings/General.vue index a175988b9..19dc8077e 100644 --- a/src/components/settings/General.vue +++ b/src/components/settings/General.vue @@ -178,10 +178,12 @@ export default { }, methods: { + /** Save the settings */ saveGeneral() { localStorage.timezone = this.$root.userTimezone; this.saveSettings(); }, + /** Get the base URL of the application */ autoGetPrimaryBaseURL() { this.settings.primaryBaseURL = location.protocol + "//" + location.host; }, diff --git a/src/components/settings/MonitorHistory.vue b/src/components/settings/MonitorHistory.vue index 0092727f4..c78c6aaf7 100644 --- a/src/components/settings/MonitorHistory.vue +++ b/src/components/settings/MonitorHistory.vue @@ -90,6 +90,7 @@ export default { }, methods: { + /** Get the current size of the database */ loadDatabaseSize() { log.debug("monitorhistory", "load database size"); this.$root.getSocket().emit("getDatabaseSize", (res) => { @@ -102,6 +103,7 @@ export default { }); }, + /** Request that the database is shrunk */ shrinkDatabase() { this.$root.getSocket().emit("shrinkDatabase", (res) => { if (res.ok) { @@ -113,10 +115,12 @@ export default { }); }, + /** Show the dialog to confirm clearing stats */ confirmClearStatistics() { this.$refs.confirmClearStatistics.show(); }, + /** Send the request to clear stats */ clearStatistics() { this.$root.clearStatistics((res) => { if (res.ok) { diff --git a/src/components/settings/ReverseProxy.vue b/src/components/settings/ReverseProxy.vue index 97db4d597..e93bd0ef2 100644 --- a/src/components/settings/ReverseProxy.vue +++ b/src/components/settings/ReverseProxy.vue @@ -120,14 +120,17 @@ export default { this.$root.getSocket().emit(prefix + "leave"); }, methods: { + /** Start the Cloudflare tunnel */ start() { this.$root.getSocket().emit(prefix + "start", this.cloudflareTunnelToken); }, + /** Stop the Cloudflare tunnel */ stop() { this.$root.getSocket().emit(prefix + "stop", this.currentPassword, (res) => { this.$root.toastRes(res); }); }, + /** Remove the token for the Cloudflare tunnel */ removeToken() { this.$root.getSocket().emit(prefix + "removeToken"); this.cloudflareTunnelToken = ""; diff --git a/src/components/settings/Security.vue b/src/components/settings/Security.vue index a5d42f82b..60e72bda2 100644 --- a/src/components/settings/Security.vue +++ b/src/components/settings/Security.vue @@ -303,6 +303,7 @@ export default { }, methods: { + /** Check new passwords match before saving them */ savePassword() { if (this.password.newPassword !== this.password.repeatNewPassword) { this.invalidPassword = true; @@ -320,6 +321,7 @@ export default { } }, + /** Disable authentication for web app access */ disableAuth() { this.settings.disableAuth = true; @@ -332,6 +334,7 @@ export default { }, this.password.currentPassword); }, + /** Enable authentication for web app access */ enableAuth() { this.settings.disableAuth = false; this.saveSettings(); From 2b42c3c828b4292fd64f0428c9a676c1bc4d6df7 Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Thu, 2 Jun 2022 00:32:05 +0100 Subject: [PATCH 3/9] Added JSDoc for src/components/* Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com> --- src/components/CertificateInfo.vue | 2 ++ src/components/CertificateInfoRow.vue | 7 +++++ src/components/Confirm.vue | 7 +++++ src/components/CopyableInput.vue | 15 ++++++++++ src/components/CountUp.vue | 6 ++-- src/components/Datetime.vue | 2 ++ src/components/HeartbeatBar.vue | 9 ++++++ src/components/HiddenInput.vue | 8 ++++++ src/components/Login.vue | 1 + src/components/MonitorList.vue | 8 ++++++ src/components/NotificationDialog.vue | 9 ++++++ src/components/PingChart.vue | 1 + src/components/ProxyDialog.vue | 7 +++++ src/components/PublicGroupList.vue | 12 ++++++++ src/components/Status.vue | 1 + src/components/Tag.vue | 6 ++++ src/components/TagsManager.vue | 41 +++++++++++++++++++++++++++ src/components/ToggleSection.vue | 2 ++ src/components/TwoFADialog.vue | 8 ++++++ src/components/Uptime.vue | 3 ++ 20 files changed, 152 insertions(+), 3 deletions(-) diff --git a/src/components/CertificateInfo.vue b/src/components/CertificateInfo.vue index bb10f158d..cb1a82917 100644 --- a/src/components/CertificateInfo.vue +++ b/src/components/CertificateInfo.vue @@ -25,10 +25,12 @@ export default { CertificateInfoRow, }, props: { + /** Object representing certificate */ certInfo: { type: Object, required: true, }, + /** Is the TLS certificate valid? */ valid: { type: Boolean, required: true, diff --git a/src/components/CertificateInfoRow.vue b/src/components/CertificateInfoRow.vue index 3ac22f3b6..f02d1d7b9 100644 --- a/src/components/CertificateInfoRow.vue +++ b/src/components/CertificateInfoRow.vue @@ -56,12 +56,19 @@ export default { Datetime, }, props: { + /** Object representing certificate */ cert: { type: Object, required: true, }, }, methods: { + /** + * Format the subject of the certificate + * @param {Object} subject Object representing the certificates + * subject + * @returns {string} + */ formatSubject(subject) { if (subject.O && subject.CN && subject.C) { return `${subject.CN} - ${subject.O} (${subject.C})`; diff --git a/src/components/Confirm.vue b/src/components/Confirm.vue index 1bfe7fe4a..d369e0b5b 100644 --- a/src/components/Confirm.vue +++ b/src/components/Confirm.vue @@ -29,14 +29,17 @@ import { Modal } from "bootstrap"; export default { props: { + /** Style of button */ btnStyle: { type: String, default: "btn-primary", }, + /** Text to use as yes */ yesText: { type: String, default: "Yes", // TODO: No idea what to translate this }, + /** Text to use as no */ noText: { type: String, default: "No", @@ -50,9 +53,13 @@ export default { this.modal = new Modal(this.$refs.modal); }, methods: { + /** Show the confirm dialog */ show() { this.modal.show(); }, + /** + * @emits string A string that simply says "yes" + */ yes() { this.$emit("yes"); }, diff --git a/src/components/CopyableInput.vue b/src/components/CopyableInput.vue index 1bccfa2ce..2e1dee766 100644 --- a/src/components/CopyableInput.vue +++ b/src/components/CopyableInput.vue @@ -25,33 +25,41 @@ let timeout; export default { props: { + /** ID of this input */ id: { type: String, default: "" }, + /** Type of input */ type: { type: String, default: "text" }, + /** The value of the input */ modelValue: { type: String, default: "" }, + /** A placeholder to use */ placeholder: { type: String, default: "" }, + /** Should the field auto complete */ autocomplete: { type: String, default: undefined, }, + /** Is the input required? */ required: { type: Boolean }, + /** Should the input be read only? */ readonly: { type: String, default: undefined, }, + /** Is the input disabled? */ disabled: { type: String, default: undefined, @@ -79,14 +87,21 @@ export default { }, methods: { + /** Show the input */ showInput() { this.visibility = "text"; }, + /** Hide the input */ hideInput() { this.visibility = "password"; }, + /** + * Copy the provided text to the users clipboard + * @param {string} textToCopy + * @returns {Promise<void>} + */ copyToClipboard(textToCopy) { this.icon = "check"; diff --git a/src/components/CountUp.vue b/src/components/CountUp.vue index df1d1ac6c..9ce68507f 100644 --- a/src/components/CountUp.vue +++ b/src/components/CountUp.vue @@ -10,6 +10,7 @@ import { sleep } from "../util.ts"; export default { props: { + /** Value to count */ value: { type: [ String, Number ], default: 0, @@ -18,6 +19,7 @@ export default { type: Number, default: 0.3, }, + /** Unit of the value */ unit: { type: String, default: "ms", @@ -43,9 +45,7 @@ export default { let frames = 12; let step = Math.floor(diff / frames); - if (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0) { - // Lazy to NOT this condition, hahaha. - } else { + if (! (isNaN(step) || ! this.isNum || (diff > 0 && step < 1) || (diff < 0 && step > 1) || diff === 0)) { for (let i = 1; i < frames; i++) { this.output += step; await sleep(15); diff --git a/src/components/Datetime.vue b/src/components/Datetime.vue index fa68d02c7..3c66f5d6e 100644 --- a/src/components/Datetime.vue +++ b/src/components/Datetime.vue @@ -13,10 +13,12 @@ dayjs.extend(relativeTime); export default { props: { + /** Value of data time */ value: { type: String, default: null, }, + /** Should only the date be displayed? */ dateOnly: { type: Boolean, default: false, diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index ce888a989..83923dd23 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -17,14 +17,17 @@ export default { props: { + /** Size of the heart beat bar */ size: { type: String, default: "big", }, + /** ID of the monitor */ monitorId: { type: Number, required: true, }, + /** Array of the monitors heart beats */ heartbeatList: { type: Array, default: null, @@ -160,12 +163,18 @@ export default { this.resize(); }, methods: { + /** Resize the heartbeat bar */ resize() { if (this.$refs.wrap) { this.maxBeat = Math.floor(this.$refs.wrap.clientWidth / (this.beatWidth + this.beatMargin * 2)); } }, + /** + * Get the title of the beat + * @param {Object} beat Beat to get title from + * @returns {string} + */ getBeatTitle(beat) { return `${this.$root.datetime(beat.time)}` + ((beat.msg) ? ` - ${beat.msg}` : ""); }, diff --git a/src/components/HiddenInput.vue b/src/components/HiddenInput.vue index d2327b9df..8fd68d4c4 100644 --- a/src/components/HiddenInput.vue +++ b/src/components/HiddenInput.vue @@ -24,25 +24,31 @@ <script> export default { props: { + /** The value of the input */ modelValue: { type: String, default: "" }, + /** A placeholder to use */ placeholder: { type: String, default: "" }, + /** Maximum lenght of the input */ maxlength: { type: Number, default: 255 }, + /** Should the field auto complete */ autocomplete: { type: String, default: undefined, }, + /** Is the input required? */ required: { type: Boolean }, + /** Should the input be read only? */ readonly: { type: String, default: undefined, @@ -68,9 +74,11 @@ export default { }, methods: { + /** Show users input in plain text */ showInput() { this.visibility = "text"; }, + /** Censor users input */ hideInput() { this.visibility = "password"; }, diff --git a/src/components/Login.vue b/src/components/Login.vue index 987651e23..3a821881d 100644 --- a/src/components/Login.vue +++ b/src/components/Login.vue @@ -55,6 +55,7 @@ export default { }; }, methods: { + /** Submit the user details and attempt to log in */ submit() { this.processing = true; diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 7aeadd1e1..1d48af710 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -58,6 +58,7 @@ export default { Tag, }, props: { + /** Should the scrollbar be shown */ scrollbar: { type: Boolean, }, @@ -124,6 +125,7 @@ export default { window.removeEventListener("scroll", this.onScroll); }, methods: { + /** Called when the user scrolls */ onScroll() { if (window.top.scrollY <= 133) { this.windowTop = window.top.scrollY; @@ -131,9 +133,15 @@ export default { this.windowTop = 133; } }, + /** + * Get URL of monitor + * @param {number} id ID of monitor + * @returns {string} Relative URL of monitor + */ monitorURL(id) { return getMonitorRelativeURL(id); }, + /** Clear the search bar */ clearSearchText() { this.searchText = ""; } diff --git a/src/components/NotificationDialog.vue b/src/components/NotificationDialog.vue index 7a1f1a100..0ca95c222 100644 --- a/src/components/NotificationDialog.vue +++ b/src/components/NotificationDialog.vue @@ -125,11 +125,16 @@ export default { }, methods: { + /** Show dialog to confirm deletion */ deleteConfirm() { this.modal.hide(); this.$refs.confirmDelete.show(); }, + /** + * Show settings for specified notification + * @param {number} notificationID ID of notification to show + */ show(notificationID) { if (notificationID) { this.id = notificationID; @@ -152,6 +157,7 @@ export default { this.modal.show(); }, + /** Submit the form to the server */ submit() { this.processing = true; this.$root.getSocket().emit("addNotification", this.notification, this.id, (res) => { @@ -170,6 +176,7 @@ export default { }); }, + /** Test the notification endpoint */ test() { this.processing = true; this.$root.getSocket().emit("testNotification", this.notification, (res) => { @@ -178,6 +185,7 @@ export default { }); }, + /** Delete the notification endpoint */ deleteNotification() { this.processing = true; 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 * @return {string} */ diff --git a/src/components/PingChart.vue b/src/components/PingChart.vue index 67bdc00c4..e472d898a 100644 --- a/src/components/PingChart.vue +++ b/src/components/PingChart.vue @@ -35,6 +35,7 @@ Chart.register(LineController, BarController, LineElement, PointElement, TimeSca export default { components: { LineChart }, props: { + /** ID of monitor */ monitorId: { type: Number, required: true, diff --git a/src/components/ProxyDialog.vue b/src/components/ProxyDialog.vue index 3070925c1..e52698f9f 100644 --- a/src/components/ProxyDialog.vue +++ b/src/components/ProxyDialog.vue @@ -130,11 +130,16 @@ export default { }, methods: { + /** Show dialog to confirm delection */ deleteConfirm() { this.modal.hide(); this.$refs.confirmDelete.show(); }, + /** + * Show settings for specified proxy + * @param {number} proxyID ID of proxy to show + */ show(proxyID) { if (proxyID) { this.id = proxyID; @@ -163,6 +168,7 @@ export default { this.modal.show(); }, + /** Submit form data for saving */ submit() { this.processing = true; this.$root.getSocket().emit("addProxy", this.proxy, this.id, (res) => { @@ -180,6 +186,7 @@ export default { }); }, + /** Delete this proxy */ deleteProxy() { this.processing = true; this.$root.getSocket().emit("deleteProxy", this.id, (res) => { diff --git a/src/components/PublicGroupList.vue b/src/components/PublicGroupList.vue index df94eec98..137f734d1 100644 --- a/src/components/PublicGroupList.vue +++ b/src/components/PublicGroupList.vue @@ -72,10 +72,12 @@ export default { Tag, }, props: { + /** Are we in edit mode? */ editMode: { type: Boolean, required: true, }, + /** Should tags be shown? */ showTags: { type: Boolean, } @@ -94,10 +96,20 @@ export default { }, methods: { + /** + * Remove the specified group + * @param {number} index Index of group to remove + */ removeGroup(index) { 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) { this.$root.publicGroupList[groupIndex].monitorList.splice(index, 1); }, diff --git a/src/components/Status.vue b/src/components/Status.vue index 1985d8518..11897047a 100644 --- a/src/components/Status.vue +++ b/src/components/Status.vue @@ -5,6 +5,7 @@ <script> export default { props: { + /** Current status of monitor */ status: { type: Number, default: 0, diff --git a/src/components/Tag.vue b/src/components/Tag.vue index 3c9668502..0325d9900 100644 --- a/src/components/Tag.vue +++ b/src/components/Tag.vue @@ -20,14 +20,20 @@ <script> export default { props: { + /** Object representing tag */ item: { type: Object, required: true, }, + /** Function to remove tag */ remove: { type: Function, default: null, }, + /** + * Size of tag + * @values normal, small + */ size: { type: String, default: "normal", diff --git a/src/components/TagsManager.vue b/src/components/TagsManager.vue index 1a9212ca5..10f6a6f7d 100644 --- a/src/components/TagsManager.vue +++ b/src/components/TagsManager.vue @@ -139,6 +139,7 @@ export default { VueMultiselect, }, props: { + /** Array of tags to be pre-selected */ preSelectedTags: { type: Array, default: () => [], @@ -241,9 +242,11 @@ export default { this.getExistingTags(); }, methods: { + /** Show the add tag dialog */ showAddDialog() { this.modal.show(); }, + /** Get all existing tags */ getExistingTags() { this.$root.getSocket().emit("getTags", (res) => { if (res.ok) { @@ -253,6 +256,10 @@ export default { } }); }, + /** + * Delete the specified tag + * @param {Object} tag Object representing tag to delete + */ deleteTag(item) { if (item.new) { // Undo Adding a new Tag @@ -262,6 +269,13 @@ export default { this.deleteTags.push(item); } }, + /** + * Set colour of text + * @param {Object} option Object representing color choice. If + * option.color is set, the text color will be white, else it + * be chosen based upon application theme + * @returns string + */ textColor(option) { if (option.color) { return "white"; @@ -269,6 +283,7 @@ export default { return this.$root.theme === "light" ? "var(--bs-body-color)" : "inherit"; } }, + /** Add a draft tag */ addDraftTag() { console.log("Adding Draft Tag: ", this.newDraftTag); if (this.newDraftTag.select != null) { @@ -296,6 +311,7 @@ export default { } this.clearDraftTag(); }, + /** Remove a draft tag */ clearDraftTag() { this.newDraftTag = { name: null, @@ -307,26 +323,51 @@ export default { }; this.modal.hide(); }, + /** + * Add a tag asynchronously + * @param {Object} newTag Object representing new tag to add + * @returns {Promise<void>} + */ addTagAsync(newTag) { return new Promise((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) { return new Promise((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) { return new Promise((resolve) => { this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve); }); }, + /** Called as user types input */ onEnter() { if (!this.validateDraftTag.invalid) { this.addDraftTag(); } }, + /** + * Submit the form data + * @param {number} monitorId ID of monitor this change affects + * @returns {void} + */ async submit(monitorId) { console.log(`Submitting tag changes for monitor ${monitorId}...`); this.processing = true; diff --git a/src/components/ToggleSection.vue b/src/components/ToggleSection.vue index daf9c00c2..5dfa3a637 100644 --- a/src/components/ToggleSection.vue +++ b/src/components/ToggleSection.vue @@ -29,10 +29,12 @@ <script> export default { props: { + /** Heading to use */ heading: { type: String, default: "", }, + /** Should the selection be open by default? */ defaultOpen: { type: Boolean, default: false, diff --git a/src/components/TwoFADialog.vue b/src/components/TwoFADialog.vue index 049f7c870..8dc9f9c79 100644 --- a/src/components/TwoFADialog.vue +++ b/src/components/TwoFADialog.vue @@ -100,18 +100,22 @@ export default { this.getStatus(); }, methods: { + /** Show the dialog */ show() { this.modal.show(); }, + /** Show dialog to confirm enabling 2FA */ confirmEnableTwoFA() { this.$refs.confirmEnableTwoFA.show(); }, + /** Show dialog to confirm disabling 2FA */ confirmDisableTwoFA() { this.$refs.confirmDisableTwoFA.show(); }, + /** Prepare 2FA configuration */ prepare2FA() { this.processing = true; @@ -126,6 +130,7 @@ export default { }); }, + /** Save the current 2FA configuration */ save2FA() { this.processing = true; @@ -143,6 +148,7 @@ export default { }); }, + /** Disable 2FA for this user */ disable2FA() { this.processing = true; @@ -160,6 +166,7 @@ export default { }); }, + /** Verify the token generated by the user */ verifyToken() { this.$root.getSocket().emit("verifyToken", this.token, this.currentPassword, (res) => { if (res.ok) { @@ -170,6 +177,7 @@ export default { }); }, + /** Get current status of 2FA */ getStatus() { this.$root.getSocket().emit("twoFAStatus", (res) => { if (res.ok) { diff --git a/src/components/Uptime.vue b/src/components/Uptime.vue index 487d62b72..8793b7a7b 100644 --- a/src/components/Uptime.vue +++ b/src/components/Uptime.vue @@ -5,14 +5,17 @@ <script> export default { props: { + /** Monitor this represents */ monitor: { type: Object, default: null, }, + /** Type of monitor */ type: { type: String, default: null, }, + /** Is this a pill */ pill: { type: Boolean, default: false, From 213aca4fc32a373dcb7db1f53f1777f0dbec0e53 Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Thu, 2 Jun 2022 10:38:17 +0100 Subject: [PATCH 4/9] Added JSDoc for src/mixins/* Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com> --- src/mixins/datetime.js | 23 +++++++++ src/mixins/lang.js | 1 + src/mixins/mobile.js | 2 + src/mixins/socket.js | 114 +++++++++++++++++++++++++++++++++++++++++ src/mixins/theme.js | 1 + 5 files changed, 141 insertions(+) diff --git a/src/mixins/datetime.js b/src/mixins/datetime.js index 7cef22d2b..698bf9e80 100644 --- a/src/mixins/datetime.js +++ b/src/mixins/datetime.js @@ -18,14 +18,31 @@ export default { }, 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) { 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) { 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) { let secondString; if (second) { @@ -36,6 +53,12 @@ export default { 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) { if (value !== undefined && value !== "") { return dayjs.utc(value).tz(this.timezone).format(format); diff --git a/src/mixins/lang.js b/src/mixins/lang.js index 31d5a8e0b..aca951498 100644 --- a/src/mixins/lang.js +++ b/src/mixins/lang.js @@ -22,6 +22,7 @@ export default { }, methods: { + /** Change the application language */ async changeLang(lang) { let message = (await langModules["../languages/" + lang + ".js"]()).default; this.$i18n.setLocaleMessage(lang, message); diff --git a/src/mixins/mobile.js b/src/mixins/mobile.js index e81ebf45c..ffc50f50c 100644 --- a/src/mixins/mobile.js +++ b/src/mixins/mobile.js @@ -12,11 +12,13 @@ export default { }, methods: { + /** Called when the screen changes size */ onResize() { this.windowWidth = window.innerWidth; this.updateBody(); }, + /** Update the document body */ updateBody() { if (this.isMobile) { document.body.classList.add("mobile"); diff --git a/src/mixins/socket.js b/src/mixins/socket.js index c54b573f3..04785b2de 100644 --- a/src/mixins/socket.js +++ b/src/mixins/socket.js @@ -62,6 +62,12 @@ export default { 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) { // No need to re-init if (this.socket.initedSocketIO) { @@ -258,10 +264,18 @@ export default { socket.on("cloudflared_token", (res) => this.cloudflared.cloudflareTunnelToken = res); }, + /** + * The storage currently in use + * @returns {Storage} + */ storage() { return (this.remember) ? localStorage : sessionStorage; }, + /** + * Get payload of JWT cookie + * @returns {(Object|undefined)} + */ getJWTPayload() { const jwtToken = this.$root.storage().token; @@ -271,10 +285,18 @@ export default { return undefined; }, + /** + * Get current socket + * @returns {Socket} + */ getSocket() { return socket; }, + /** + * Show success or error toast dependant on response status code + * @param {Object} res Response object + */ toastRes(res) { if (res.ok) { toast.success(res.msg); @@ -283,14 +305,35 @@ export default { } }, + /** + * Show a success toast + * @param {string} msg Message to show + */ toastSuccess(msg) { toast.success(msg); }, + /** + * Show an error toast + * @param {string} msg Message to show + */ toastError(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) { socket.emit("login", { username, @@ -315,6 +358,10 @@ export default { }); }, + /** + * Log in using a token + * @param {string} token Token to log in with + */ loginByToken(token) { socket.emit("loginByToken", token, (res) => { this.allowLoginDialog = true; @@ -328,6 +375,7 @@ export default { }); }, + /** Log out of the web application */ logout() { socket.emit("logout", () => { }); this.storage().removeItem("token"); @@ -337,26 +385,54 @@ export default { this.clearData(); }, + /** + * Callback for general socket requests + * @callback socketCB + * @param {Object} res Result of operation + */ + /** Prepare 2FA configuration */ prepare2FA(callback) { socket.emit("prepare2FA", callback); }, + /** + * Save the current 2FA configuration + * @param {any} secret Unused + * @param {socketCB} callback + */ save2FA(secret, callback) { socket.emit("save2FA", callback); }, + /** + * Disable 2FA for this user + * @param {socketCB} callback + */ disable2FA(callback) { socket.emit("disable2FA", callback); }, + /** + * Verify the provided 2FA token + * @param {string} token Token to verify + * @param {socketCB} callback + */ verifyToken(token, callback) { socket.emit("verifyToken", token, callback); }, + /** + * Get current 2FA status + * @param {socketCB} callback + */ twoFAStatus(callback) { socket.emit("twoFAStatus", callback); }, + /** + * Get list of monitors + * @param {socketCB} callback + */ getMonitorList(callback) { if (! callback) { callback = () => { }; @@ -364,36 +440,74 @@ export default { socket.emit("getMonitorList", callback); }, + /** + * Add a monitor + * @param {Object} monitor Object representing monitor to add + * @param {socketCB} callback + */ 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) { socket.emit("deleteMonitor", monitorID, callback); }, + /** Clear the hearbeat list */ clearData() { console.log("reset heartbeat list"); this.heartbeatList = {}; 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) { 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) { 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) { socket.emit("clearHeartbeats", monitorID, callback); }, + /** + * Clear all statistics + * @param {socketCB} callback + */ 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) { socket.emit("getMonitorBeats", monitorID, period, callback); } diff --git a/src/mixins/theme.js b/src/mixins/theme.js index 21ebd0916..58f4d91b9 100644 --- a/src/mixins/theme.js +++ b/src/mixins/theme.js @@ -75,6 +75,7 @@ export default { }, methods: { + /** Update the theme color meta tag */ updateThemeColorMeta() { if (this.theme === "dark") { document.querySelector("#theme-color").setAttribute("content", "#161B22"); From 217022903159e0c010ceda2df1ea589003c70319 Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Thu, 2 Jun 2022 10:42:37 +0100 Subject: [PATCH 5/9] Improve JSDoc for some components Apply suggestions from code review Co-authored-by: Nelson Chan <chakflying@hotmail.com> --- src/components/Confirm.vue | 2 +- src/components/HeartbeatBar.vue | 7 ++++--- src/components/HiddenInput.vue | 2 +- src/components/TagsManager.vue | 4 ++-- src/components/ToggleSection.vue | 4 ++-- 5 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/components/Confirm.vue b/src/components/Confirm.vue index d369e0b5b..258724a39 100644 --- a/src/components/Confirm.vue +++ b/src/components/Confirm.vue @@ -58,7 +58,7 @@ export default { this.modal.show(); }, /** - * @emits string A string that simply says "yes" + * @emits "yes" - Notify the parent when Yes is pressed */ yes() { this.$emit("yes"); diff --git a/src/components/HeartbeatBar.vue b/src/components/HeartbeatBar.vue index 83923dd23..028b8e335 100644 --- a/src/components/HeartbeatBar.vue +++ b/src/components/HeartbeatBar.vue @@ -17,7 +17,7 @@ export default { props: { - /** Size of the heart beat bar */ + /** Size of the heartbeat bar */ size: { type: String, default: "big", @@ -27,7 +27,7 @@ export default { type: Number, required: true, }, - /** Array of the monitors heart beats */ + /** Array of the monitors heartbeats */ heartbeatList: { type: Array, default: null, @@ -171,7 +171,8 @@ export default { }, /** - * Get the title of the beat + * 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} */ diff --git a/src/components/HiddenInput.vue b/src/components/HiddenInput.vue index 8fd68d4c4..6287af05c 100644 --- a/src/components/HiddenInput.vue +++ b/src/components/HiddenInput.vue @@ -34,7 +34,7 @@ export default { type: String, default: "" }, - /** Maximum lenght of the input */ + /** Maximum length of the input */ maxlength: { type: Number, default: 255 diff --git a/src/components/TagsManager.vue b/src/components/TagsManager.vue index 10f6a6f7d..fd94c93e2 100644 --- a/src/components/TagsManager.vue +++ b/src/components/TagsManager.vue @@ -270,7 +270,7 @@ export default { } }, /** - * Set colour of text + * Get colour of text inside the tag * @param {Object} option Object representing color choice. If * option.color is set, the text color will be white, else it * be chosen based upon application theme @@ -357,7 +357,7 @@ export default { this.$root.getSocket().emit("deleteMonitorTag", tagId, monitorId, value, resolve); }); }, - /** Called as user types input */ + /** Handle pressing Enter key when inside the modal */ onEnter() { if (!this.validateDraftTag.invalid) { this.addDraftTag(); diff --git a/src/components/ToggleSection.vue b/src/components/ToggleSection.vue index 5dfa3a637..47dc21905 100644 --- a/src/components/ToggleSection.vue +++ b/src/components/ToggleSection.vue @@ -29,12 +29,12 @@ <script> export default { props: { - /** Heading to use */ + /** Heading of the Section */ heading: { type: String, default: "", }, - /** Should the selection be open by default? */ + /** Should the section be open by default? */ defaultOpen: { type: Boolean, default: false, From b0476cfb5b4f5da694e6c6056f2cc37e0246d73e Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Thu, 2 Jun 2022 13:46:44 +0100 Subject: [PATCH 6/9] Added JSDoc for src/pages/* Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com> --- src/pages/AddStatusPage.vue | 1 + src/pages/Details.vue | 15 +++++++++++++ src/pages/EditMonitor.vue | 23 ++++++++++++++++---- src/pages/ManageStatusPage.vue | 5 +++++ src/pages/NotFound.vue | 1 + src/pages/Settings.vue | 15 +++++++++++-- src/pages/Setup.vue | 4 ++++ src/pages/StatusPage.vue | 39 ++++++++++++++++++++++++++++++---- 8 files changed, 93 insertions(+), 10 deletions(-) diff --git a/src/pages/AddStatusPage.vue b/src/pages/AddStatusPage.vue index e0200177e..230894ff4 100644 --- a/src/pages/AddStatusPage.vue +++ b/src/pages/AddStatusPage.vue @@ -51,6 +51,7 @@ export default { }; }, methods: { + /** Submit form data to add new status page */ async submit() { this.processing = true; diff --git a/src/pages/Details.vue b/src/pages/Details.vue index d40561fe0..f5728d661 100644 --- a/src/pages/Details.vue +++ b/src/pages/Details.vue @@ -289,39 +289,47 @@ export default { }, methods: { + /** Request a test notification be sent for this monitor */ testNotification() { this.$root.getSocket().emit("testNotification", this.monitor.id); toast.success("Test notification is requested."); }, + /** Show dialog to confirm pause */ pauseDialog() { this.$refs.confirmPause.show(); }, + /** Resume this monitor */ resumeMonitor() { this.$root.getSocket().emit("resumeMonitor", this.monitor.id, (res) => { this.$root.toastRes(res); }); }, + /** Request that this monitor is paused */ pauseMonitor() { this.$root.getSocket().emit("pauseMonitor", this.monitor.id, (res) => { this.$root.toastRes(res); }); }, + /** Show dialog to confirm deletion */ deleteDialog() { this.$refs.confirmDelete.show(); }, + /** Show dialog to confirm clearing events */ clearEventsDialog() { this.$refs.confirmClearEvents.show(); }, + /** Show dialog to confirm clearing heartbeats */ clearHeartbeatsDialog() { this.$refs.confirmClearHeartbeats.show(); }, + /** Request that this monitor is deleted */ deleteMonitor() { this.$root.deleteMonitor(this.monitor.id, (res) => { if (res.ok) { @@ -333,6 +341,7 @@ export default { }); }, + /** Request that this monitors events are cleared */ clearEvents() { this.$root.clearEvents(this.monitor.id, (res) => { if (! res.ok) { @@ -341,6 +350,7 @@ export default { }); }, + /** Request that this monitors heartbeats are cleared */ clearHeartbeats() { this.$root.clearHeartbeats(this.monitor.id, (res) => { 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) { let translationPrefix = ""; if (average) { diff --git a/src/pages/EditMonitor.vue b/src/pages/EditMonitor.vue index 1cb3684be..0b196ae25 100644 --- a/src/pages/EditMonitor.vue +++ b/src/pages/EditMonitor.vue @@ -522,6 +522,7 @@ export default { this.dnsresolvetypeOptions = dnsresolvetypeOptions; }, methods: { + /** Initialize the edit monitor form */ init() { if (this.isAdd) { @@ -578,6 +579,10 @@ export default { }, + /** + * Validate form input + * @returns {boolean} Is the form input valid? + */ isInputValid() { if (this.monitor.body) { try { @@ -598,6 +603,10 @@ export default { return true; }, + /** + * Submit the form data for processing + * @returns {void} + */ async submit() { this.processing = true; @@ -642,14 +651,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) { 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) { this.monitor.proxyId = id; }, diff --git a/src/pages/ManageStatusPage.vue b/src/pages/ManageStatusPage.vue index c3360375c..275fd4900 100644 --- a/src/pages/ManageStatusPage.vue +++ b/src/pages/ManageStatusPage.vue @@ -51,6 +51,11 @@ export default { }, methods: { + /** + * Get the correct URL for the icon + * @param {string} icon Path for icon + * @returns {string} Correctly formatted path including port numbers + */ icon(icon) { if (icon === "/icon.svg") { return icon; diff --git a/src/pages/NotFound.vue b/src/pages/NotFound.vue index 410c16a81..6fb7e2a77 100644 --- a/src/pages/NotFound.vue +++ b/src/pages/NotFound.vue @@ -44,6 +44,7 @@ export default { }, methods: { + /** Go back 1 in browser history */ goBack() { history.back(); } diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index 11d3a1be3..c118226c9 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -118,13 +118,17 @@ export default { methods: { - // For desktop only, mobile do nothing + /** + * Load the general settings page + * For desktop only, mobile do nothing + */ loadGeneralPage() { if (!this.currentPage && !this.$root.isMobile) { this.$router.push("/settings/general"); } }, + /** Load settings from server */ loadSettings() { this.$root.getSocket().emit("getSettings", (res) => { this.settings = res.data; @@ -149,9 +153,16 @@ export default { }); }, + /** + * Callback for saving settings + * @callback saveSettingsCB + * @param {Object} res Result of operation + */ + /** * 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) { this.$root.getSocket().emit("setSettings", this.settings, currentPassword, (res) => { diff --git a/src/pages/Setup.vue b/src/pages/Setup.vue index ab5952167..08347b8e1 100644 --- a/src/pages/Setup.vue +++ b/src/pages/Setup.vue @@ -71,6 +71,10 @@ export default { }); }, methods: { + /** + * Submit form data for processing + * @returns {void} + */ submit() { this.processing = true; diff --git a/src/pages/StatusPage.vue b/src/pages/StatusPage.vue index fbcbf9e8e..1d6481417 100644 --- a/src/pages/StatusPage.vue +++ b/src/pages/StatusPage.vue @@ -332,6 +332,7 @@ export default { }, props: { + /** Override for the status page slug */ overrideSlug: { type: String, required: false, @@ -582,10 +583,16 @@ export default { } }, + /** + * Provide syntax highlighting for CSS + * @param {string} code Text to highlight + * @returns {string} + */ highlighter(code) { return highlight(code, languages.css); }, + /** Update the heartbeat list and update favicon if neccessary */ updateHeartbeatList() { // If editMode, it will use the data from websocket. if (! this.editMode) { @@ -614,6 +621,7 @@ export default { } }, + /** Enable editing mode */ edit() { if (this.hasToken) { this.$root.initSocketIO(true); @@ -622,6 +630,7 @@ export default { } }, + /** Save the status page */ save() { let startTime = new Date(); this.config.slug = this.config.slug.trim().toLowerCase(); @@ -649,10 +658,12 @@ export default { }); }, + /** Show dialog confirming deletion */ deleteDialog() { this.$refs.confirmDelete.show(); }, + /** Request deletion of this status page */ deleteStatusPage() { this.$root.getSocket().emit("deleteStatusPage", this.slug, (res) => { if (res.ok) { @@ -664,10 +675,16 @@ export default { }); }, + /** + * Returns label for a specifed monitor + * @param {Object} monitor Object representing monitor + * @returns {string} + */ monitorSelectorLabel(monitor) { return `${monitor.name}`; }, + /** Add a group to the status page */ addGroup() { let groupName = this.$t("Untitled Group"); @@ -681,27 +698,32 @@ export default { }); }, + /** Add a domain to the status page */ addDomainField() { this.config.domainNameList.push(""); }, + /** Discard changes to status page */ discard() { 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) { this.imgDataUrl = imgDataUrl; }, + /** Show image crop dialog if in edit mode */ showImageCropUploadMethod() { if (this.editMode) { this.showImageCropUpload = true; } }, + /** Create an incident for this status page */ createIncident() { this.enableEditIncidentMode = true; @@ -716,6 +738,7 @@ export default { }; }, + /** Post the incident to the status page */ postIncident() { if (this.incident.title === "" || this.incident.content === "") { toast.error(this.$t("Please input title and content")); @@ -735,14 +758,13 @@ export default { }, - /** - * Click Edit Button - */ + /** Click Edit Button */ editIncident() { this.enableEditIncidentMode = true; this.previousIncident = Object.assign({}, this.incident); }, + /** Cancel creation or editing of incident */ cancelIncident() { this.enableEditIncidentMode = false; @@ -752,16 +774,25 @@ export default { } }, + /** Unpin the incident */ unpinIncident() { this.$root.getSocket().emit("unpinIncident", this.slug, () => { this.incident = null; }); }, + /** + * Get the relative time difference of a date from now + * @returns {string} + */ dateFromNow(date) { return dayjs.utc(date).fromNow(); }, + /** + * Remove a domain from the status page + * @param {number} index Index of domain to remove + */ removeDomain(index) { this.config.domainNameList.splice(index, 1); }, From c94dcf15335e7c0313ef3236400650ea6b5dcd58 Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Thu, 2 Jun 2022 14:32:38 +0100 Subject: [PATCH 7/9] Added JSDoc for src/* Signed-off-by: Matthew Nickson <mnickson@sidingsmedia.com> --- src/util-frontend.js | 7 ++-- src/util.js | 70 ++++++++++++++++++++++++++++++++++++---- src/util.ts | 76 ++++++++++++++++++++++++++++++++++++++------ 3 files changed, 135 insertions(+), 18 deletions(-) diff --git a/src/util-frontend.js b/src/util-frontend.js index 565b053c4..36dac49f9 100644 --- a/src/util-frontend.js +++ b/src/util-frontend.js @@ -26,8 +26,8 @@ function getTimezoneOffset(timeZone) { /** * Returns a list of timezones sorted by their offset from UTC. -* @param {Array} timezones - An array of timezone objects. -* @returns {Array} A list of the given timezones sorted by their offset from UTC. +* @param {Object[]} timezones An array of timezone objects. +* @returns {Object[]} A list of the given timezones sorted by their offset from UTC. * * Generated by Trelent */ @@ -63,6 +63,7 @@ export function timezoneList() { return result; } +/** Set the locale of the HTML page */ export function setPageLocale() { const html = document.documentElement; 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. + * @returns {string} */ export function getResBaseURL() { const env = process.env.NODE_ENV; diff --git a/src/util.js b/src/util.js index ee6095e08..d5e3617fd 100644 --- a/src/util.js +++ b/src/util.js @@ -18,6 +18,7 @@ exports.PENDING = 2; exports.STATUS_PAGE_ALL_DOWN = 0; exports.STATUS_PAGE_ALL_UP = 1; exports.STATUS_PAGE_PARTIAL_DOWN = 2; +/** Flip the status of s */ function flipStatus(s) { if (s === exports.UP) { return exports.DOWN; @@ -28,6 +29,10 @@ function flipStatus(s) { return s; } exports.flipStatus = flipStatus; +/** + * Delays for specified number of seconds + * @param ms Number of milliseconds to sleep for + */ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } @@ -83,6 +88,12 @@ class Logger { 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) { if (this.hideLog[level] && this.hideLog[level].includes(module)) { return; @@ -109,18 +120,44 @@ class Logger { console.log(formattedMessage); } } + /** + * Log an INFO message + * @param module Module log comes from + * @param msg Message to write + */ info(module, msg) { this.log(module, msg, "info"); } + /** + * Log a WARN message + * @param module Module log comes from + * @param msg Message to write + */ warn(module, msg) { this.log(module, msg, "warn"); } + /** + * Log an ERROR message + * @param module Module log comes from + * @param msg Message to write + */ error(module, msg) { this.log(module, msg, "error"); } + /** + * Log a DEBUG message + * @param module Module log comes from + * @param msg Message to write + */ debug(module, msg) { 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) { let finalMessage = exception; if (msg) { @@ -130,13 +167,13 @@ class 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() { - /** - * 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) { String.prototype.replaceAll = function (str, newStr) { // If a regex pattern @@ -153,6 +190,10 @@ class TimeLogger { constructor() { this.startTime = dayjs().valueOf(); } + /** + * Output time since start of monitor + * @param name Name of monitor + */ print(name) { if (exports.isDev && process.env.TIMELOGGER === "1") { console.log(name + ": " + (dayjs().valueOf() - this.startTime) + "ms"); @@ -201,6 +242,13 @@ let getRandomBytes = ((typeof window !== 'undefined' && window.crypto) : function () { 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) { // synchronous version of: https://github.com/joepie91/node-random-number-csprng const range = max - min; @@ -231,6 +279,11 @@ function getCryptoRandomInt(min, max) { } } exports.getCryptoRandomInt = getCryptoRandomInt; +/** + * Generate a secret + * @param length Lenght of secret to generate + * @returns + */ function genSecret(length = 64) { let secret = ""; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -241,6 +294,11 @@ function genSecret(length = 64) { return secret; } exports.genSecret = genSecret; +/** + * Get the path of a monitor + * @param id ID of monitor + * @returns Formatted relative path + */ function getMonitorRelativeURL(id) { return "/dashboard/" + id; } diff --git a/src/util.ts b/src/util.ts index 057090b72..b28ba3c15 100644 --- a/src/util.ts +++ b/src/util.ts @@ -19,7 +19,7 @@ export const STATUS_PAGE_ALL_DOWN = 0; export const STATUS_PAGE_ALL_UP = 1; export const STATUS_PAGE_PARTIAL_DOWN = 2; - +/** Flip the status of s */ export function flipStatus(s: number) { if (s === UP) { return DOWN; @@ -32,6 +32,10 @@ export function flipStatus(s: number) { return s; } +/** + * Delays for specified number of seconds + * @param ms Number of milliseconds to sleep for + */ export function sleep(ms: number) { 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) { if (this.hideLog[level] && this.hideLog[level].includes(module)) { 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) { 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) { 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"); } - 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"); } + /** + * 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) { let finalMessage = exception @@ -151,13 +187,13 @@ export const log = new Logger(); 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() { - /** - * 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) { String.prototype.replaceAll = function (str: string, newStr: string) { // If a regex pattern @@ -177,7 +213,10 @@ export class TimeLogger { constructor() { this.startTime = dayjs().valueOf(); } - + /** + * Output time since start of monitor + * @param name Name of monitor + */ print(name: string) { if (isDev && process.env.TIMELOGGER === "1") { 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 { // 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 secret + * @param length Lenght of secret to generate + * @returns + */ export function genSecret(length = 64) { let secret = ""; const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; @@ -277,6 +328,11 @@ export function genSecret(length = 64) { return secret; } +/** + * Get the path of a monitor + * @param id ID of monitor + * @returns Formatted relative path + */ export function getMonitorRelativeURL(id: string) { return "/dashboard/" + id; } From 0e28707307b77fbcec8d6aa7473322f47adfe304 Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Thu, 2 Jun 2022 15:15:21 +0100 Subject: [PATCH 8/9] Minor formatting for JSDoc comments Added a number of minor formatting changes to JSDoc comments in /src --- src/components/Confirm.vue | 2 +- src/components/Datetime.vue | 2 +- src/components/MonitorList.vue | 2 +- src/components/ToggleSection.vue | 2 +- src/components/Uptime.vue | 2 +- src/mixins/mobile.js | 2 +- src/pages/Settings.vue | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Confirm.vue b/src/components/Confirm.vue index 258724a39..1a1addc6e 100644 --- a/src/components/Confirm.vue +++ b/src/components/Confirm.vue @@ -58,7 +58,7 @@ export default { this.modal.show(); }, /** - * @emits "yes" - Notify the parent when Yes is pressed + * @emits string "yes" Notify the parent when Yes is pressed */ yes() { this.$emit("yes"); diff --git a/src/components/Datetime.vue b/src/components/Datetime.vue index 3c66f5d6e..b24ab0b3c 100644 --- a/src/components/Datetime.vue +++ b/src/components/Datetime.vue @@ -13,7 +13,7 @@ dayjs.extend(relativeTime); export default { props: { - /** Value of data time */ + /** Value of date time */ value: { type: String, default: null, diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 1d48af710..1ae12b2ff 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -125,7 +125,7 @@ export default { window.removeEventListener("scroll", this.onScroll); }, methods: { - /** Called when the user scrolls */ + /** Handle user scroll */ onScroll() { if (window.top.scrollY <= 133) { this.windowTop = window.top.scrollY; diff --git a/src/components/ToggleSection.vue b/src/components/ToggleSection.vue index 47dc21905..a2f7c9dae 100644 --- a/src/components/ToggleSection.vue +++ b/src/components/ToggleSection.vue @@ -29,7 +29,7 @@ <script> export default { props: { - /** Heading of the Section */ + /** Heading of the section */ heading: { type: String, default: "", diff --git a/src/components/Uptime.vue b/src/components/Uptime.vue index 8793b7a7b..8f7eba80c 100644 --- a/src/components/Uptime.vue +++ b/src/components/Uptime.vue @@ -15,7 +15,7 @@ export default { type: String, default: null, }, - /** Is this a pill */ + /** Is this a pill? */ pill: { type: Boolean, default: false, diff --git a/src/mixins/mobile.js b/src/mixins/mobile.js index ffc50f50c..32ee209fc 100644 --- a/src/mixins/mobile.js +++ b/src/mixins/mobile.js @@ -12,7 +12,7 @@ export default { }, methods: { - /** Called when the screen changes size */ + /** Handle screen resize */ onResize() { this.windowWidth = window.innerWidth; this.updateBody(); diff --git a/src/pages/Settings.vue b/src/pages/Settings.vue index c118226c9..f8effb4ad 100644 --- a/src/pages/Settings.vue +++ b/src/pages/Settings.vue @@ -120,7 +120,7 @@ export default { /** * Load the general settings page - * For desktop only, mobile do nothing + * For desktop only, on mobile do nothing */ loadGeneralPage() { if (!this.currentPage && !this.$root.isMobile) { From a927f5cd1589e1ee55ddb5ef01687dfa58a3cbc9 Mon Sep 17 00:00:00 2001 From: Matthew Nickson <mnickson@sidingsmedia.com> Date: Thu, 2 Jun 2022 16:40:56 +0100 Subject: [PATCH 9/9] Fixed typos + improved clarity and detail of some JSDoc Apply suggestions from code review Co-authored-by: Nelson Chan <chakflying@hotmail.com> --- src/components/ProxyDialog.vue | 2 +- src/components/TagsManager.vue | 6 +++--- src/mixins/mobile.js | 2 +- src/util.ts | 6 +++--- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/components/ProxyDialog.vue b/src/components/ProxyDialog.vue index e52698f9f..434c571be 100644 --- a/src/components/ProxyDialog.vue +++ b/src/components/ProxyDialog.vue @@ -130,7 +130,7 @@ export default { }, methods: { - /** Show dialog to confirm delection */ + /** Show dialog to confirm deletion */ deleteConfirm() { this.modal.hide(); this.$refs.confirmDelete.show(); diff --git a/src/components/TagsManager.vue b/src/components/TagsManager.vue index fd94c93e2..2958babe8 100644 --- a/src/components/TagsManager.vue +++ b/src/components/TagsManager.vue @@ -271,9 +271,9 @@ export default { }, /** * Get colour of text inside the tag - * @param {Object} option Object representing color choice. If - * option.color is set, the text color will be white, else it - * be chosen based upon application theme + * @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) { diff --git a/src/mixins/mobile.js b/src/mixins/mobile.js index 32ee209fc..00ea88866 100644 --- a/src/mixins/mobile.js +++ b/src/mixins/mobile.js @@ -18,7 +18,7 @@ export default { this.updateBody(); }, - /** Update the document body */ + /** Add css-class "mobile" to body if needed */ updateBody() { if (this.isMobile) { document.body.classList.add("mobile"); diff --git a/src/util.ts b/src/util.ts index b28ba3c15..b69f31ace 100644 --- a/src/util.ts +++ b/src/util.ts @@ -314,9 +314,9 @@ export function getCryptoRandomInt(min: number, max: number):number { } /** - * Generate a secret - * @param length Lenght of secret to generate - * @returns + * Generate a random alphanumeric string of fixed length + * @param length Length of string to generate + * @returns string */ export function genSecret(length = 64) { let secret = "";