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": "Серый",