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 > <div class="invalid-feedback"> - {{ $t("Tag with this name already exist.") }} + {{ + nameInvalid + ? $t("Tag with this name already exist.") + : $t("Tag name contains a comma.") + }} </div> </div> @@ -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 @@ </div> </div> <div class="header-filter"> - <MonitorListFilter :filterState="filterState" @update-filter="updateFilter" /> + <MonitorListFilter @update-filter="updateFilter" /> </div> <!-- Selection Controls --> @@ -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 @@ <font-awesome-icon v-if="numFiltersActive > 0" icon="times" /> </button> <MonitorListFilterDropdown - :filterActive="filterState.status?.length > 0" + :filterActive="this.$router.currentRoute.value.query?.status?.length > 0" > <template #status> - <Status v-if="filterState.status?.length === 1" :status="filterState.status[0]" /> + <Status v-if="this.$router.currentRoute.value.query?.status?.length === 1" :status="this.$router.currentRoute.value.query?.status[0]" /> <span v-else> {{ $t('Status') }} </span> @@ -29,7 +29,7 @@ <Status :status="1" /> <span class="ps-3"> {{ $root.stats.up }} - <span v-if="filterState.status?.includes(1)" class="px-1 filter-active"> + <span v-if="this.$router.currentRoute.value.query?.status?.includes('up')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -42,7 +42,7 @@ <Status :status="0" /> <span class="ps-3"> {{ $root.stats.down }} - <span v-if="filterState.status?.includes(0)" class="px-1 filter-active"> + <span v-if="this.$router.currentRoute.value.query?.status?.includes('down')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -55,7 +55,7 @@ <Status :status="2" /> <span class="ps-3"> {{ $root.stats.pending }} - <span v-if="filterState.status?.includes(2)" class="px-1 filter-active"> + <span v-if="this.$router.currentRoute.value.query?.status?.includes('pending')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -68,7 +68,7 @@ <Status :status="3" /> <span class="ps-3"> {{ $root.stats.maintenance }} - <span v-if="filterState.status?.includes(3)" class="px-1 filter-active"> + <span v-if="this.$router.currentRoute.value.query?.status?.includes('maintenance')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -77,10 +77,10 @@ </li> </template> </MonitorListFilterDropdown> - <MonitorListFilterDropdown :filterActive="filterState.active?.length > 0"> + <MonitorListFilterDropdown :filterActive="this.$router.currentRoute.value.query?.active?.length > 0"> <template #status> - <span v-if="filterState.active?.length === 1"> - <span v-if="filterState.active[0]">{{ $t("Running") }}</span> + <span v-if="this.$router.currentRoute.value.query?.active?.length === 1"> + <span v-if="this.$router.currentRoute.value.query?.active[0]">{{ $t("Running") }}</span> <span v-else>{{ $t("filterActivePaused") }}</span> </span> <span v-else> @@ -94,7 +94,7 @@ <span>{{ $t("Running") }}</span> <span class="ps-3"> {{ $root.stats.active }} - <span v-if="filterState.active?.includes(true)" class="px-1 filter-active"> + <span v-if="this.$router.currentRoute.value.query?.active?.includes(true)" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -107,7 +107,7 @@ <span>{{ $t("filterActivePaused") }}</span> <span class="ps-3"> {{ $root.stats.pause }} - <span v-if="filterState.active?.includes(false)" class="px-1 filter-active"> + <span v-if="this.$router.currentRoute.value.query?.active?.includes(false)" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -116,11 +116,11 @@ </li> </template> </MonitorListFilterDropdown> - <MonitorListFilterDropdown :filterActive="filterState.tags?.length > 0"> + <MonitorListFilterDropdown :filterActive="this.$router.currentRoute.value.query?.tags?.length > 0"> <template #status> <Tag - v-if="filterState.tags?.length === 1" - :item="tagsList.find(tag => tag.id === filterState.tags[0])" + v-if="this.$router.currentRoute.value.query?.tags?.length === 1" + :item="tagsList.find(tag => tag.id === this.$router.currentRoute.value.query?.tags[0])" :size="'sm'" /> <span v-else> @@ -134,7 +134,7 @@ <span><Tag :item="tag" :size="'sm'" /></span> <span class="ps-3"> {{ getTaggedMonitorCount(tag) }} - <span v-if="filterState.tags?.includes(tag.id)" class="px-1 filter-active"> + <span v-if="this.$router.currentRoute.value.query?.tags?.split(',').includes(tag.name)" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -162,23 +162,26 @@ export default { Status, Tag, }, - props: { - filterState: { - type: Object, - required: true, - } - }, emits: [ "updateFilter" ], data() { return { tagsList: [], + filterNames: [ + 'status', + 'active', + 'tags', + ], }; }, computed: { numFiltersActive() { let num = 0; - Object.values(this.filterState).forEach(item => { + Object.values( + Array.from(Object.entries(this.$router.currentRoute.value.query)).filter( + e => this.filterNames.includes(e[0]) + ) + ).forEach(item => { if (item != null && item.length > 0) { num += 1; } @@ -191,52 +194,63 @@ export default { this.getExistingTags(); }, methods: { + getActiveFilters: function() { + const filters = Object.fromEntries( + Array.from(Object.entries(this.$router.currentRoute.value.query ?? {})) + ); + + return { + status: filters['status'] ? filters['status'].split(',') : [], + active: filters['active'] ? filters['active'].split(',') : [], + tags: filters['tags'] ? filters['tags'].split(',') : [], + }; + }, toggleStatusFilter(status) { let newFilter = { - ...this.filterState + ...this.getActiveFilters(), }; - if (newFilter.status == null) { - newFilter.status = [ status ]; + const statusStates = { + 1: 'up', + 0: 'down', + 2: 'pending', + 3: 'maintenance', + }; + + const finalStatus = statusStates[status]; + + if (newFilter.status.includes(''+finalStatus)) { + newFilter.status = newFilter.status.filter(item => item !== ''+finalStatus); } else { - if (newFilter.status.includes(status)) { - newFilter.status = newFilter.status.filter(item => item !== status); - } else { - newFilter.status.push(status); - } + newFilter.status.push(finalStatus); } + this.$emit("updateFilter", newFilter); }, toggleActiveFilter(active) { let newFilter = { - ...this.filterState + ...this.getActiveFilters(), }; - if (newFilter.active == null) { - newFilter.active = [ active ]; + if (newFilter.active.includes(''+active)) { + newFilter.active = newFilter.active.filter(item => item !== ''+active); } else { - if (newFilter.active.includes(active)) { - newFilter.active = newFilter.active.filter(item => item !== active); - } else { - newFilter.active.push(active); - } + newFilter.active.push(active); } + this.$emit("updateFilter", newFilter); }, toggleTagFilter(tag) { let newFilter = { - ...this.filterState + ...this.getActiveFilters(), }; - if (newFilter.tags == null) { - newFilter.tags = [ tag.id ]; + if (newFilter.tags.includes(''+tag.name)) { + newFilter.tags = newFilter.tags.filter(item => item !== ''+tag.name); } else { - if (newFilter.tags.includes(tag.id)) { - newFilter.tags = newFilter.tags.filter(item => item !== tag.id); - } else { - newFilter.tags.push(tag.id); - } + newFilter.tags.push(tag.name); } + this.$emit("updateFilter", newFilter); }, clearFilters() { From 5c46af6019b0e074a3fd3c1caa4a883949d7d952 Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Sat, 17 Feb 2024 18:31:58 +0100 Subject: [PATCH 05/10] Chore: Add translations for "Tag name contains a comma" --- src/lang/en.json | 1 + src/lang/ru-RU.json | 1 + 2 files changed, 2 insertions(+) diff --git a/src/lang/en.json b/src/lang/en.json index 0f59e62ae..d7bd0fc08 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -182,6 +182,7 @@ "Add New below or Select...": "Add New below or Select…", "Tag with this name already exist.": "Tag with this name already exists.", "Tag with this value already exist.": "Tag with this value already exists.", + "Tag name contains a comma.": "Tag name contains a comma.", "color": "Color", "value (optional)": "value (optional)", "Gray": "Gray", diff --git a/src/lang/ru-RU.json b/src/lang/ru-RU.json index 76a16c8ee..cc746be7e 100644 --- a/src/lang/ru-RU.json +++ b/src/lang/ru-RU.json @@ -161,6 +161,7 @@ "Add New below or Select...": "Добавить новый или выбрать…", "Tag with this name already exist.": "Такой тег уже существует.", "Tag with this value already exist.": "Тег с таким значением уже существует.", + "Tag name contains a comma.": "Название тега содержит запятую.", "color": "цвет", "value (optional)": "значение (необязательно)", "Gray": "Серый", From 4b0ce4857e85d11338564498bf2d12133525b910 Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Sat, 17 Feb 2024 18:36:29 +0100 Subject: [PATCH 06/10] Chore: Run lint --- src/components/MonitorList.vue | 16 +++---- src/components/MonitorListFilter.vue | 64 ++++++++++++++-------------- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index c790c4d95..40a21bd8a 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -100,16 +100,16 @@ export default { down: 0, pending: 2, maintenance: 3, - 1: 'up', - 0: 'down', - 2: 'pending', - 3: 'maintenance', + 1: "up", + 0: "down", + 2: "pending", + 3: "maintenance", }, activeStates: { running: true, paused: false, - true: 'running', - false: 'paused', + true: "running", + false: "paused", }, }; }, @@ -293,11 +293,11 @@ export default { updateFilter(newFilter) { const newQuery = { ...this.$router.currentRoute.value.query }; - for (const [key, value] of Object.entries(newFilter)) { + for (const [ key, value ] of Object.entries(newFilter)) { if (!value || (value instanceof Array && value.length === 0)) { delete newQuery[key]; - continue + continue; } newQuery[key] = value instanceof Array diff --git a/src/components/MonitorListFilter.vue b/src/components/MonitorListFilter.vue index 8262af6b7..a0afc7c11 100644 --- a/src/components/MonitorListFilter.vue +++ b/src/components/MonitorListFilter.vue @@ -14,10 +14,10 @@ <font-awesome-icon v-if="numFiltersActive > 0" icon="times" /> </button> <MonitorListFilterDropdown - :filterActive="this.$router.currentRoute.value.query?.status?.length > 0" + :filterActive="$router.currentRoute.value.query?.status?.length > 0" > <template #status> - <Status v-if="this.$router.currentRoute.value.query?.status?.length === 1" :status="this.$router.currentRoute.value.query?.status[0]" /> + <Status v-if="$router.currentRoute.value.query?.status?.length === 1" :status="$router.currentRoute.value.query?.status[0]" /> <span v-else> {{ $t('Status') }} </span> @@ -29,7 +29,7 @@ <Status :status="1" /> <span class="ps-3"> {{ $root.stats.up }} - <span v-if="this.$router.currentRoute.value.query?.status?.includes('up')" class="px-1 filter-active"> + <span v-if="$router.currentRoute.value.query?.status?.includes('up')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -42,7 +42,7 @@ <Status :status="0" /> <span class="ps-3"> {{ $root.stats.down }} - <span v-if="this.$router.currentRoute.value.query?.status?.includes('down')" class="px-1 filter-active"> + <span v-if="$router.currentRoute.value.query?.status?.includes('down')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -55,7 +55,7 @@ <Status :status="2" /> <span class="ps-3"> {{ $root.stats.pending }} - <span v-if="this.$router.currentRoute.value.query?.status?.includes('pending')" class="px-1 filter-active"> + <span v-if="$router.currentRoute.value.query?.status?.includes('pending')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -68,7 +68,7 @@ <Status :status="3" /> <span class="ps-3"> {{ $root.stats.maintenance }} - <span v-if="this.$router.currentRoute.value.query?.status?.includes('maintenance')" class="px-1 filter-active"> + <span v-if="$router.currentRoute.value.query?.status?.includes('maintenance')" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -77,10 +77,10 @@ </li> </template> </MonitorListFilterDropdown> - <MonitorListFilterDropdown :filterActive="this.$router.currentRoute.value.query?.active?.length > 0"> + <MonitorListFilterDropdown :filterActive="$router.currentRoute.value.query?.active?.length > 0"> <template #status> - <span v-if="this.$router.currentRoute.value.query?.active?.length === 1"> - <span v-if="this.$router.currentRoute.value.query?.active[0]">{{ $t("Running") }}</span> + <span v-if="$router.currentRoute.value.query?.active?.length === 1"> + <span v-if="$router.currentRoute.value.query?.active[0]">{{ $t("Running") }}</span> <span v-else>{{ $t("filterActivePaused") }}</span> </span> <span v-else> @@ -94,7 +94,7 @@ <span>{{ $t("Running") }}</span> <span class="ps-3"> {{ $root.stats.active }} - <span v-if="this.$router.currentRoute.value.query?.active?.includes(true)" class="px-1 filter-active"> + <span v-if="$router.currentRoute.value.query?.active?.includes(true)" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -107,7 +107,7 @@ <span>{{ $t("filterActivePaused") }}</span> <span class="ps-3"> {{ $root.stats.pause }} - <span v-if="this.$router.currentRoute.value.query?.active?.includes(false)" class="px-1 filter-active"> + <span v-if="$router.currentRoute.value.query?.active?.includes(false)" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -116,11 +116,11 @@ </li> </template> </MonitorListFilterDropdown> - <MonitorListFilterDropdown :filterActive="this.$router.currentRoute.value.query?.tags?.length > 0"> + <MonitorListFilterDropdown :filterActive="$router.currentRoute.value.query?.tags?.length > 0"> <template #status> <Tag - v-if="this.$router.currentRoute.value.query?.tags?.length === 1" - :item="tagsList.find(tag => tag.id === this.$router.currentRoute.value.query?.tags[0])" + v-if="$router.currentRoute.value.query?.tags?.length === 1" + :item="tagsList.find(tag => tag.id === $router.currentRoute.value.query?.tags[0])" :size="'sm'" /> <span v-else> @@ -134,7 +134,7 @@ <span><Tag :item="tag" :size="'sm'" /></span> <span class="ps-3"> {{ getTaggedMonitorCount(tag) }} - <span v-if="this.$router.currentRoute.value.query?.tags?.split(',').includes(tag.name)" class="px-1 filter-active"> + <span v-if="$router.currentRoute.value.query?.tags?.split(',').includes(tag.name)" class="px-1 filter-active"> <font-awesome-icon icon="check" /> </span> </span> @@ -167,9 +167,9 @@ export default { return { tagsList: [], filterNames: [ - 'status', - 'active', - 'tags', + "status", + "active", + "tags", ], }; }, @@ -194,15 +194,15 @@ export default { this.getExistingTags(); }, methods: { - getActiveFilters: function() { + getActiveFilters: function () { const filters = Object.fromEntries( Array.from(Object.entries(this.$router.currentRoute.value.query ?? {})) ); return { - status: filters['status'] ? filters['status'].split(',') : [], - active: filters['active'] ? filters['active'].split(',') : [], - tags: filters['tags'] ? filters['tags'].split(',') : [], + status: filters["status"] ? filters["status"].split(",") : [], + active: filters["active"] ? filters["active"].split(",") : [], + tags: filters["tags"] ? filters["tags"].split(",") : [], }; }, toggleStatusFilter(status) { @@ -211,16 +211,16 @@ export default { }; const statusStates = { - 1: 'up', - 0: 'down', - 2: 'pending', - 3: 'maintenance', + 1: "up", + 0: "down", + 2: "pending", + 3: "maintenance", }; const finalStatus = statusStates[status]; - if (newFilter.status.includes(''+finalStatus)) { - newFilter.status = newFilter.status.filter(item => item !== ''+finalStatus); + if (newFilter.status.includes("" + finalStatus)) { + newFilter.status = newFilter.status.filter(item => item !== "" + finalStatus); } else { newFilter.status.push(finalStatus); } @@ -232,8 +232,8 @@ export default { ...this.getActiveFilters(), }; - if (newFilter.active.includes(''+active)) { - newFilter.active = newFilter.active.filter(item => item !== ''+active); + if (newFilter.active.includes("" + active)) { + newFilter.active = newFilter.active.filter(item => item !== "" + active); } else { newFilter.active.push(active); } @@ -245,8 +245,8 @@ export default { ...this.getActiveFilters(), }; - if (newFilter.tags.includes(''+tag.name)) { - newFilter.tags = newFilter.tags.filter(item => item !== ''+tag.name); + if (newFilter.tags.includes("" + tag.name)) { + newFilter.tags = newFilter.tags.filter(item => item !== "" + tag.name); } else { newFilter.tags.push(tag.name); } From 57f79d231b2f223d7e7259b62ed412647b1021f5 Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Mon, 11 Mar 2024 23:12:39 +0100 Subject: [PATCH 07/10] Fix: Remove URL parameter access by property name --- src/components/MonitorList.vue | 38 ++++-------- src/components/MonitorListFilter.vue | 86 ++++++++++++++-------------- 2 files changed, 53 insertions(+), 71 deletions(-) diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue index 40a21bd8a..f3dc11414 100644 --- a/src/components/MonitorList.vue +++ b/src/components/MonitorList.vue @@ -95,22 +95,6 @@ export default { disableSelectAllWatcher: false, selectedMonitors: {}, windowTop: 0, - 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: { @@ -233,26 +217,27 @@ export default { }); })(); - const fetchedTagNames = tagParams + const fetchedTagIDs = tagParams ? tagParams .split(",") .map(identifier => { const tagID = parseInt(identifier, 10); - return tags - .find(t => t.name === identifier || t.id === tagID) - ?.name ?? 0; + if (isNaN(tagID)) { + return; + } + return tags.find(t => t.tag_id === tagID)?.id ?? 1; }) .filter(tagID => tagID !== 0) : undefined; this.updateFilter({ status: statusParams ? statusParams.split(",").map( - status => this.statusStates[this.statusStates[status.trim()]] + status => status.trim() ) : queryParams?.["status"], active: activeParams ? activeParams.split(",").map( - active => this.activeStates[this.activeStates[active.trim()]] + active => active.trim() ) : queryParams?.["active"], - tags: tagParams ? fetchedTagNames : queryParams?.["tags"], + tags: tagParams ? fetchedTagIDs : queryParams?.["tags"], }); }, beforeUnmount() { @@ -398,7 +383,7 @@ export default { if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) { monitor.status = this.$root.lastHeartbeatList[monitor.id].status; } - statusMatch = this.$router.currentRoute.value.query?.status.includes(this.statusStates[monitor.status]); + statusMatch = this.$router.currentRoute.value.query?.status.includes(monitor.status); } // filter by active @@ -411,9 +396,8 @@ export default { let tagsMatch = true; 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; + const monitorTagIds = monitor.tags.map(tag => tag.tag_id); + tagsMatch = tagsInURL.map(Number).some(tagId => monitorTagIds.includes(tagId)); } return searchTextMatch && statusMatch && activeMatch && tagsMatch; diff --git a/src/components/MonitorListFilter.vue b/src/components/MonitorListFilter.vue index a0afc7c11..315704253 100644 --- a/src/components/MonitorListFilter.vue +++ b/src/components/MonitorListFilter.vue @@ -29,7 +29,10 @@ <Status :status="1" /> <span class="ps-3"> {{ $root.stats.up }} - <span v-if="$router.currentRoute.value.query?.status?.includes('up')" class="px-1 filter-active"> + <span + v-if="$router.currentRoute.value.query?.status?.includes('1')" + class="px-1 filter-active" + > <font-awesome-icon icon="check" /> </span> </span> @@ -42,7 +45,10 @@ <Status :status="0" /> <span class="ps-3"> {{ $root.stats.down }} - <span v-if="$router.currentRoute.value.query?.status?.includes('down')" class="px-1 filter-active"> + <span + v-if="$router.currentRoute.value.query?.status?.includes('0')" + class="px-1 filter-active" + > <font-awesome-icon icon="check" /> </span> </span> @@ -55,7 +61,10 @@ <Status :status="2" /> <span class="ps-3"> {{ $root.stats.pending }} - <span v-if="$router.currentRoute.value.query?.status?.includes('pending')" class="px-1 filter-active"> + <span + v-if="$router.currentRoute.value.query?.status?.includes('2')" + class="px-1 filter-active" + > <font-awesome-icon icon="check" /> </span> </span> @@ -68,7 +77,10 @@ <Status :status="3" /> <span class="ps-3"> {{ $root.stats.maintenance }} - <span v-if="$router.currentRoute.value.query?.status?.includes('maintenance')" class="px-1 filter-active"> + <span + v-if="$router.currentRoute.value.query?.status?.includes('3')" + class="px-1 filter-active" + > <font-awesome-icon icon="check" /> </span> </span> @@ -94,7 +106,10 @@ <span>{{ $t("Running") }}</span> <span class="ps-3"> {{ $root.stats.active }} - <span v-if="$router.currentRoute.value.query?.active?.includes(true)" class="px-1 filter-active"> + <span + v-if="$router.currentRoute.value.query?.active?.includes(true)" + class="px-1 filter-active" + > <font-awesome-icon icon="check" /> </span> </span> @@ -107,7 +122,10 @@ <span>{{ $t("filterActivePaused") }}</span> <span class="ps-3"> {{ $root.stats.pause }} - <span v-if="$router.currentRoute.value.query?.active?.includes(false)" class="px-1 filter-active"> + <span + v-if="$router.currentRoute.value.query?.active?.includes(false)" + class="px-1 filter-active" + > <font-awesome-icon icon="check" /> </span> </span> @@ -119,9 +137,8 @@ <MonitorListFilterDropdown :filterActive="$router.currentRoute.value.query?.tags?.length > 0"> <template #status> <Tag - v-if="$router.currentRoute.value.query?.tags?.length === 1" - :item="tagsList.find(tag => tag.id === $router.currentRoute.value.query?.tags[0])" - :size="'sm'" + v-if="$router.currentRoute.value.query?.tags?.split?.(',')?.length === 1 && tagsList.find(tag => tag.id === +$router.currentRoute.value.query?.tags?.split?.(',')?.[0])" + :item="tagsList.find(tag => tag.id === +$router.currentRoute.value.query?.tags?.split?.(',')?.[0])" :size="'sm'" /> <span v-else> {{ $t('Tags') }} @@ -131,10 +148,15 @@ <li v-for="tag in tagsList" :key="tag.id"> <div class="dropdown-item" tabindex="0" @click.stop="toggleTagFilter(tag)"> <div class="d-flex align-items-center justify-content-between"> - <span><Tag :item="tag" :size="'sm'" /></span> + <span> + <Tag :item="tag" :size="'sm'" /> + </span> <span class="ps-3"> {{ getTaggedMonitorCount(tag) }} - <span v-if="$router.currentRoute.value.query?.tags?.split(',').includes(tag.name)" class="px-1 filter-active"> + <span + v-if="$router.currentRoute.value.query?.tags?.split(',').includes(tag.id)" + class="px-1 filter-active" + > <font-awesome-icon icon="check" /> </span> </span> @@ -166,28 +188,13 @@ export default { data() { return { tagsList: [], - filterNames: [ - "status", - "active", - "tags", - ], }; }, computed: { numFiltersActive() { - let num = 0; - - Object.values( - Array.from(Object.entries(this.$router.currentRoute.value.query)).filter( - e => this.filterNames.includes(e[0]) - ) - ).forEach(item => { - if (item != null && item.length > 0) { - num += 1; - } - }); - - return num; + return this.$router.currentRoute.value.query.status?.length > 0 ? 1 : 0 + + this.$router.currentRoute.value.query.active?.length > 0 ? 1 : 0 + + this.$router.currentRoute.value.query.tags?.length > 0 ? 1 : 0; } }, mounted() { @@ -210,19 +217,10 @@ export default { ...this.getActiveFilters(), }; - const statusStates = { - 1: "up", - 0: "down", - 2: "pending", - 3: "maintenance", - }; - - const finalStatus = statusStates[status]; - - if (newFilter.status.includes("" + finalStatus)) { - newFilter.status = newFilter.status.filter(item => item !== "" + finalStatus); + if (newFilter.status.includes("" + status)) { + newFilter.status = newFilter.status.filter(item => item !== "" + status); } else { - newFilter.status.push(finalStatus); + newFilter.status.push(status); } this.$emit("updateFilter", newFilter); @@ -245,10 +243,10 @@ export default { ...this.getActiveFilters(), }; - if (newFilter.tags.includes("" + tag.name)) { - newFilter.tags = newFilter.tags.filter(item => item !== "" + tag.name); + if (newFilter.tags.includes("" + tag.id)) { + newFilter.tags = newFilter.tags.filter(item => item !== "" + tag.id); } else { - newFilter.tags.push(tag.name); + newFilter.tags.push(tag.id); } this.$emit("updateFilter", newFilter); From 07f02b2ceb3331c95e73aa790a3c5147e359d517 Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Mon, 11 Mar 2024 23:29:20 +0100 Subject: [PATCH 08/10] Fix: Search filters not resetting --- src/components/MonitorListFilter.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/MonitorListFilter.vue b/src/components/MonitorListFilter.vue index 315704253..c4cd82773 100644 --- a/src/components/MonitorListFilter.vue +++ b/src/components/MonitorListFilter.vue @@ -154,7 +154,7 @@ <span class="ps-3"> {{ getTaggedMonitorCount(tag) }} <span - v-if="$router.currentRoute.value.query?.tags?.split(',').includes(tag.id)" + v-if="$router.currentRoute.value.query?.tags?.split(',').includes(''+tag.id)" class="px-1 filter-active" > <font-awesome-icon icon="check" /> @@ -253,7 +253,9 @@ export default { }, clearFilters() { this.$emit("updateFilter", { - status: null, + status: undefined, + active: undefined, + tags: undefined, }); }, getExistingTags() { From a39e117933727ff1a40c9681df741f1f40f523fb Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:00:13 +0100 Subject: [PATCH 09/10] Fix: Remove requested changes --- src/components/MonitorListFilter.vue | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/MonitorListFilter.vue b/src/components/MonitorListFilter.vue index c4cd82773..578387b18 100644 --- a/src/components/MonitorListFilter.vue +++ b/src/components/MonitorListFilter.vue @@ -202,9 +202,7 @@ export default { }, methods: { getActiveFilters: function () { - const filters = Object.fromEntries( - Array.from(Object.entries(this.$router.currentRoute.value.query ?? {})) - ); + const filters = this.$router.currentRoute.value.query; return { status: filters["status"] ? filters["status"].split(",") : [], From 0fcb56f59c122e757b53eb7cbf29fe9cf76fe281 Mon Sep 17 00:00:00 2001 From: NihadBadalov <32594553+NihadBadalov@users.noreply.github.com> Date: Tue, 12 Mar 2024 11:04:57 +0100 Subject: [PATCH 10/10] Chore: Remove translation to avoid conflicts --- src/lang/ru-RU.json | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lang/ru-RU.json b/src/lang/ru-RU.json index cc746be7e..76a16c8ee 100644 --- a/src/lang/ru-RU.json +++ b/src/lang/ru-RU.json @@ -161,7 +161,6 @@ "Add New below or Select...": "Добавить новый или выбрать…", "Tag with this name already exist.": "Такой тег уже существует.", "Tag with this value already exist.": "Тег с таким значением уже существует.", - "Tag name contains a comma.": "Название тега содержит запятую.", "color": "цвет", "value (optional)": "значение (необязательно)", "Gray": "Серый",