diff --git a/src/components/MonitorList.vue b/src/components/MonitorList.vue
index a579316b3..88450948b 100644
--- a/src/components/MonitorList.vue
+++ b/src/components/MonitorList.vue
@@ -89,17 +89,11 @@ export default {
},
data() {
return {
- searchText: "",
selectMode: false,
selectAll: false,
disableSelectAllWatcher: false,
selectedMonitors: {},
windowTop: 0,
- filterState: {
- status: null,
- active: null,
- tags: null,
- }
};
},
computed: {
@@ -164,12 +158,66 @@ export default {
return Object.keys(this.selectedMonitors).length;
},
+ /**
+ * Returns applied filters based on query params.
+ * @returns {{ status: number[], active: any[], tags: number[] }} The current filter state.
+ */
+ filterState() {
+ // Since query params are always strings, convert them to the correct type
+ let status = this.$route.query["status"] || [];
+ if (!Array.isArray(status)) {
+ status = [ status ];
+ }
+ status = status.map(Number);
+ // Casting to boolean does not work here as Boolean("false") === true
+ let active = this.$route.query["active"] || [];
+ if (!Array.isArray(active)) {
+ active = [ active ];
+ }
+ active = active.map(val => {
+ if (val === "true") {
+ return true;
+ }
+ if (val === "false") {
+ return false;
+ }
+ return val;
+ });
+ let tags = this.$route.query["tags"] || [];
+ if (!Array.isArray(tags)) {
+ tags = [ tags ];
+ }
+ tags = tags.map(Number);
+ return {
+ status,
+ active,
+ tags,
+ };
+ },
+
+ searchText: {
+ get() {
+ return this.$route.query.searchText || "";
+ },
+ set(value) {
+ let newQuery = { ...this.$route.query };
+ if (value === "") {
+ delete newQuery.searchText;
+ } else {
+ newQuery.searchText = value;
+ }
+ this.$router.replace({
+ query: newQuery,
+ });
+ }
+ },
+
/**
* Determines if any filters are active.
* @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.filterState.status.length > 0 || this.filterState.active.length > 0 || this.filterState.tags.length > 0 || this.searchText !== "";
}
},
watch: {
@@ -239,11 +287,16 @@ export default {
},
/**
* Update the MonitorList Filter
- * @param {object} newFilter Object with new filter
+ * @param {{ status: number[], active: any[], tags: number[] }} newFilter Object with new filter
* @returns {void}
*/
updateFilter(newFilter) {
- this.filterState = newFilter;
+ this.$router.replace({
+ query: {
+ ...this.$route.query,
+ ...newFilter,
+ },
+ });
},
/**
* Deselect a monitor
@@ -333,7 +386,7 @@ export default {
// filter by status
let statusMatch = true;
- if (this.filterState.status != null && this.filterState.status.length > 0) {
+ if (this.filterState.status.length > 0) {
if (monitor.id in this.$root.lastHeartbeatList && this.$root.lastHeartbeatList[monitor.id]) {
monitor.status = this.$root.lastHeartbeatList[monitor.id].status;
}
@@ -342,13 +395,13 @@ export default {
// filter by active
let activeMatch = true;
- if (this.filterState.active != null && this.filterState.active.length > 0) {
+ if (this.filterState.active.length > 0) {
activeMatch = this.filterState.active.includes(monitor.active);
}
// filter by tags
let tagsMatch = true;
- if (this.filterState.tags != null && this.filterState.tags.length > 0) {
+ if (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
.length > 0;
diff --git a/src/components/MonitorListFilter.vue b/src/components/MonitorListFilter.vue
index 330efc9be..5f223d60c 100644
--- a/src/components/MonitorListFilter.vue
+++ b/src/components/MonitorListFilter.vue
@@ -14,10 +14,10 @@
-
+
{{ $t('Status') }}
@@ -29,7 +29,7 @@
{{ $root.stats.up }}
-
+
@@ -42,7 +42,7 @@
{{ $root.stats.down }}
-
+
@@ -55,7 +55,7 @@
{{ $root.stats.pending }}
-
+
@@ -68,7 +68,7 @@
{{ $root.stats.maintenance }}
-
+
@@ -77,11 +77,12 @@
-
+
-
- {{ $t("Running") }}
- {{ $t("filterActivePaused") }}
+
+ {{ $t("Running") }}
+ {{ $t("filterActivePaused") }}
+ {{ $t("Unknown") }}
{{ $t("filterActive") }}
@@ -94,7 +95,7 @@
{{ $t("Running") }}
{{ $root.stats.active }}
-
+
@@ -107,7 +108,7 @@
{{ $t("filterActivePaused") }}
{{ $root.stats.pause }}
-
+
@@ -116,10 +117,11 @@
-
+
+
@@ -134,7 +136,7 @@
{{ getTaggedMonitorCount(tag) }}
-
+
@@ -179,16 +181,37 @@ export default {
let num = 0;
Object.values(this.filterState).forEach(item => {
- if (item != null && item.length > 0) {
+ if (item.length > 0) {
num += 1;
}
});
return num;
+ },
+ /**
+ * Returns an array of invalid filters assuming tagsList has been fetched
+ * @returns {Array} Array of invalid filters
+ */
+ invalidFilters() {
+ const filters = [];
+ if (!this.filterState.status.every((val) => val >= 0 && val <= 3)) {
+ filters.push(this.$t("Status"));
+ }
+ if (!this.filterState.active.every((val) => val === true || val === false)) {
+ filters.push(this.$t("Active"));
+ }
+ if (!this.filterState.tags.every((val) => this.tagsList.find(tag => tag.id === val))) {
+ filters.push(this.$t("Tags"));
+ }
+ return filters;
}
},
mounted() {
- this.getExistingTags();
+ this.getExistingTags(() => {
+ if (this.invalidFilters.length > 0) {
+ this.$root.toastError(this.$t("InvalidFilters", [ this.invalidFilters.join(", ") ]));
+ }
+ });
},
methods: {
toggleStatusFilter(status) {
@@ -196,14 +219,10 @@ export default {
...this.filterState
};
- if (newFilter.status == null) {
- newFilter.status = [ status ];
+ if (newFilter.status.includes(status)) {
+ newFilter.status = newFilter.status.filter(item => item !== status);
} else {
- if (newFilter.status.includes(status)) {
- newFilter.status = newFilter.status.filter(item => item !== status);
- } else {
- newFilter.status.push(status);
- }
+ newFilter.status.push(status);
}
this.$emit("updateFilter", newFilter);
},
@@ -212,14 +231,10 @@ export default {
...this.filterState
};
- 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);
},
@@ -228,27 +243,24 @@ export default {
...this.filterState
};
- if (newFilter.tags == null) {
- newFilter.tags = [ tag.id ];
+ if (newFilter.tags.includes(tag.id)) {
+ newFilter.tags = newFilter.tags.filter(item => item !== tag.id);
} 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.id);
}
this.$emit("updateFilter", newFilter);
},
clearFilters() {
this.$emit("updateFilter", {
- status: null,
+ status: [],
});
},
- getExistingTags() {
+ getExistingTags(callback) {
this.$root.getSocket().emit("getTags", (res) => {
if (res.ok) {
this.tagsList = res.tags;
}
+ callback();
});
},
getTaggedMonitorCount(tag) {
diff --git a/src/lang/en.json b/src/lang/en.json
index e215f1031..970bdf2ff 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -1051,5 +1051,6 @@
"RabbitMQ Password": "RabbitMQ Password",
"rabbitmqHelpText": "To use the monitor, you will need to enable the Management Plugin in your RabbitMQ setup. For more information, please consult the {rabitmq_documentation}.",
"SendGrid API Key": "SendGrid API Key",
- "Separate multiple email addresses with commas": "Separate multiple email addresses with commas"
+ "Separate multiple email addresses with commas": "Separate multiple email addresses with commas",
+ "InvalidFilters": "The following filters are invalid: {0}"
}
diff --git a/src/router.js b/src/router.js
index bda5078e1..9a2db859d 100644
--- a/src/router.js
+++ b/src/router.js
@@ -192,8 +192,26 @@ const routes = [
},
];
-export const router = createRouter({
+const router = createRouter({
linkActiveClass: "active",
history: createWebHistory(),
routes,
});
+
+router.beforeEach((to, from) => {
+ // If the path is same, either the query or has has changed so avoid changing query params
+ // Without this check, modifying any query params will be blocked
+ // Check if redirectedFrom is defined to check if this function has already been run
+ // Without this check, the router will be stuck in an infinite loop
+ if (to.fullPath !== from.fullPath && !to.redirectedFrom) {
+ return {
+ ...to,
+ query: {
+ ...to.query,
+ ...from.query,
+ },
+ };
+ }
+});
+
+export { router };