From 091dc068397ef84cd2d7801b71b2058015c68360 Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:25:07 +0100 Subject: [PATCH 01/10] Feat: Support URL parameters to change Dashboard filters --- src/components/MonitorList.vue | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index b9d42048b..5dc4af31b 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -206,6 +206,29 @@ export default { }, mounted() { window.addEventListener("scroll", this.onScroll); + + const url = new URL(location.href); + const params = url.searchParams; + const filterParam = params.get("filter"); + const statusParams = params.getAll("status"); + + if (filterParam !== "true") { + return; + } + + const states = { + up: 1, + down: 0, + pending: 2, + maintenance: 3, + }; + + this.updateFilter({ + ...this.filterState, + status: statusParams.map( + status => states[status] + ), + }); }, beforeUnmount() { window.removeEventListener("scroll", this.onScroll); From f38da99c11f86f046e05d5c0a4358c5c62e43d0f Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Thu, 15 Feb 2024 21:18:49 +0100 Subject: [PATCH 02/10] Feat: Add URL filtering to the remaining filters - Add URL filtering to the remaining filters - Remove the required `filter=true` parameter - Use vue-router instead of `URL` --- src/components/MonitorList.vue | 49 +++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 13 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 5dc4af31b..bdb2b7056 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -204,30 +204,53 @@ export default { } }, }, - mounted() { + async mounted() { window.addEventListener("scroll", this.onScroll); - const url = new URL(location.href); - const params = url.searchParams; - const filterParam = params.get("filter"); - const statusParams = params.getAll("status"); + const statusParams = this.$router.currentRoute.value.query["status"]; + const activeParams = this.$router.currentRoute.value.query["active"]; + const tagParams = this.$router.currentRoute.value.query["tags"]; - if (filterParam !== "true") { - return; - } - - const states = { + const statusStates = { up: 1, down: 0, pending: 2, maintenance: 3, }; + const activeStates = { + running: true, + paused: false, + }; + + const tags = await (() => { + return new Promise((resolve) => { + this.$root.getSocket().emit("getTags", (res) => { + if (res.ok) { + resolve(res.tags); + } + }); + }); + })(); + + const fetchedTagIDs = tagParams + .split(",") + .map(identifier => { + const tagID = parseInt(identifier, 10); + return tags + .find(t => t.name === identifier || t.id === tagID) + ?.id ?? 0; + }); + this.updateFilter({ ...this.filterState, - status: statusParams.map( - status => states[status] - ), + status: statusParams ? statusParams.split(",").map( + status => statusStates[status.trim()] + ) : this.filterState["status"], + active: activeParams ? activeParams.split(",").map( + active => activeStates[active.trim()] + ) : this.filterState["active"], + tags: tagParams ? fetchedTagIDs : this.filterState["tags"], }); }, beforeUnmount() { From d3a5b224cc63fa18108976cd46488ca8b27dc7a9 Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Sat, 17 Feb 2024 18:28:33 +0100 Subject: [PATCH 03/10] Feat: Don't allow commas in tag names --- src/components/TagEditDialog.vue | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/TagEditDialog.vue b/src/components/TagEditDialog.vue index b77967c77..e82338cdd 100644 --- a/src/components/TagEditDialog.vue +++ b/src/components/TagEditDialog.vue @@ -17,11 +17,15 @@ v-model="tag.name" type="text" class="form-control" - :class="{'is-invalid': nameInvalid}" + :class="{'is-invalid': nameInvalid || nameContainsComma}" required >
- {{ $t("Tag with this name already exist.") }} + {{ + nameInvalid + ? $t("Tag with this name already exist.") + : $t("Tag name contains a comma.") + }}
@@ -160,6 +164,7 @@ export default { addingMonitor: [], selectedAddMonitor: null, nameInvalid: false, + nameContainsComma: false, }; }, @@ -260,6 +265,13 @@ export default { this.nameInvalid = true; return false; } + + this.nameContainsComma = false; + if (this.tag?.name?.includes(",")) { + this.nameContainsComma = true; + return false; + } + return true; }, From 3d4fb163d50a3e188d69f726ac74c9dc4f1b421e Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Sat, 17 Feb 2024 18:30:39 +0100 Subject: [PATCH 04/10] Feat: Handle monitor filter state in URL --- src/components/MonitorList.vue | 104 +++++++++++++++----------- src/components/MonitorListFilter.vue | 106 +++++++++++++++------------ 2 files changed, 120 insertions(+), 90 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index bdb2b7056..c790c4d95 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -26,7 +26,7 @@
- +
@@ -95,11 +95,22 @@ export default { disableSelectAllWatcher: false, selectedMonitors: {}, windowTop: 0, - filterState: { - status: null, - active: null, - tags: null, - } + statusStates: { + up: 1, + down: 0, + pending: 2, + maintenance: 3, + 1: 'up', + 0: 'down', + 2: 'pending', + 3: 'maintenance', + }, + activeStates: { + running: true, + paused: false, + true: 'running', + false: 'paused', + }, }; }, computed: { @@ -169,7 +180,7 @@ export default { * @returns {boolean} True if any filter is active, false otherwise. */ filtersActive() { - return this.filterState.status != null || this.filterState.active != null || this.filterState.tags != null || this.searchText !== ""; + return this.$router.currentRoute.value.query?.status != null || this.$router.currentRoute.value.query?.active != null || this.$router.currentRoute.value.query?.tags != null || this.searchText !== ""; } }, watch: { @@ -207,21 +218,10 @@ export default { async mounted() { window.addEventListener("scroll", this.onScroll); - const statusParams = this.$router.currentRoute.value.query["status"]; - const activeParams = this.$router.currentRoute.value.query["active"]; - const tagParams = this.$router.currentRoute.value.query["tags"]; - - const statusStates = { - up: 1, - down: 0, - pending: 2, - maintenance: 3, - }; - - const activeStates = { - running: true, - paused: false, - }; + const queryParams = this.$router.currentRoute.value.query; + const statusParams = queryParams?.["status"]; + const activeParams = queryParams?.["active"]; + const tagParams = queryParams?.["tags"]; const tags = await (() => { return new Promise((resolve) => { @@ -233,24 +233,26 @@ export default { }); })(); - const fetchedTagIDs = tagParams - .split(",") - .map(identifier => { - const tagID = parseInt(identifier, 10); - return tags - .find(t => t.name === identifier || t.id === tagID) - ?.id ?? 0; - }); + const fetchedTagNames = tagParams + ? tagParams + .split(",") + .map(identifier => { + const tagID = parseInt(identifier, 10); + return tags + .find(t => t.name === identifier || t.id === tagID) + ?.name ?? 0; + }) + .filter(tagID => tagID !== 0) + : undefined; this.updateFilter({ - ...this.filterState, status: statusParams ? statusParams.split(",").map( - status => statusStates[status.trim()] - ) : this.filterState["status"], + status => this.statusStates[this.statusStates[status.trim()]] + ) : queryParams?.["status"], active: activeParams ? activeParams.split(",").map( - active => activeStates[active.trim()] - ) : this.filterState["active"], - tags: tagParams ? fetchedTagIDs : this.filterState["tags"], + active => this.activeStates[this.activeStates[active.trim()]] + ) : queryParams?.["active"], + tags: tagParams ? fetchedTagNames : queryParams?.["tags"], }); }, beforeUnmount() { @@ -289,7 +291,20 @@ export default { * @returns {void} */ updateFilter(newFilter) { - this.filterState = newFilter; + const newQuery = { ...this.$router.currentRoute.value.query }; + + for (const [key, value] of Object.entries(newFilter)) { + if (!value + || (value instanceof Array && value.length === 0)) { + delete newQuery[key]; + continue + } + + newQuery[key] = value instanceof Array + ? value.length > 0 ? value.join(",") : null + : value; + } + this.$router.push({ query: newQuery }); }, /** * Deselect a monitor @@ -379,24 +394,25 @@ export default { // filter by status let statusMatch = true; - if (this.filterState.status != null && this.filterState.status.length > 0) { + if (this.$router.currentRoute.value.query?.status != null && this.$router.currentRoute.value.query?.status.length > 0) { if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) { monitor.status = this.$root.lastHeartbeatList[monitor.id].status; } - statusMatch = this.filterState.status.includes(monitor.status); + statusMatch = this.$router.currentRoute.value.query?.status.includes(this.statusStates[monitor.status]); } // filter by active let activeMatch = true; - if (this.filterState.active != null && this.filterState.active.length > 0) { - activeMatch = this.filterState.active.includes(monitor.active); + if (this.$router.currentRoute.value.query?.active != null && this.$router.currentRoute.value.query?.active.length > 0) { + activeMatch = this.$router.currentRoute.value.query?.active.includes(monitor.active); } // filter by tags let tagsMatch = true; - if (this.filterState.tags != null && this.filterState.tags.length > 0) { - tagsMatch = monitor.tags.map(tag => tag.tag_id) // convert to array of tag IDs - .filter(monitorTagId => this.filterState.tags.includes(monitorTagId)) // perform Array Intersaction between filter and monitor's tags + const tagsInURL = this.$router.currentRoute.value.query?.tags?.split(",") || []; + if (this.$router.currentRoute.value.query?.tags != null && this.$router.currentRoute.value.query?.tags.length > 0) { + tagsMatch = monitor.tags.map(tag => tag.name) // convert to array of tag names + .filter(monitorTagId => tagsInURL.includes(monitorTagId)) // perform Array Intersaction between filter and monitor's tags .length > 0; } diff --git a/src/components/MonitorListFilter.vue b/src/components/MonitorListFilter.vue index 330efc9be..8262af6b7 100644 --- a/src/components/MonitorListFilter.vue +++ b/src/components/MonitorListFilter.vue @@ -14,10 +14,10 @@ - + - +