diff --git a/src/Frontend/env.d.ts b/src/Frontend/env.d.ts index 25bf2dc33..14b42e970 100644 --- a/src/Frontend/env.d.ts +++ b/src/Frontend/env.d.ts @@ -10,6 +10,7 @@ declare global { service_control_url: string; monitoring_urls: string[]; showPendingRetry: boolean; + showAllMessages: boolean; }; } } diff --git a/src/Frontend/src/components/PageHeader.vue b/src/Frontend/src/components/PageHeader.vue index b47af2f52..2fdf59ed6 100644 --- a/src/Frontend/src/components/PageHeader.vue +++ b/src/Frontend/src/components/PageHeader.vue @@ -12,6 +12,7 @@ import EventsMenuItem from "@/components/events/EventsMenuItem.vue"; import DashboardMenuItem from "@/components/dashboard/DashboardMenuItem.vue"; import FeedbackButton from "@/components/FeedbackButton.vue"; import ThroughputMenuItem from "@/views/throughputreport/ThroughputMenuItem.vue"; +import AuditMenuItem from "./audit/AuditMenuItem.vue"; // prettier-ignore const menuItems = computed( @@ -19,6 +20,7 @@ const menuItems = computed( DashboardMenuItem, HeartbeatsMenuItem, ...(useIsMonitoringEnabled() ? [MonitoringMenuItem] : []), + ...(window.defaultConfig.showAllMessages ? [AuditMenuItem] : []), FailedMessagesMenuItem, CustomChecksMenuItem, EventsMenuItem, diff --git a/src/Frontend/src/components/RefreshConfig.vue b/src/Frontend/src/components/RefreshConfig.vue new file mode 100644 index 000000000..78a0f641b --- /dev/null +++ b/src/Frontend/src/components/RefreshConfig.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/Frontend/src/components/audit/AuditList.vue b/src/Frontend/src/components/audit/AuditList.vue new file mode 100644 index 000000000..f0aaf3d1f --- /dev/null +++ b/src/Frontend/src/components/audit/AuditList.vue @@ -0,0 +1,224 @@ + + + + + diff --git a/src/Frontend/src/components/audit/AuditMenuItem.vue b/src/Frontend/src/components/audit/AuditMenuItem.vue new file mode 100644 index 000000000..1be2dd5ee --- /dev/null +++ b/src/Frontend/src/components/audit/AuditMenuItem.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/src/Frontend/src/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue b/src/Frontend/src/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue deleted file mode 100644 index 43fcad227..000000000 --- a/src/Frontend/src/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue +++ /dev/null @@ -1,16 +0,0 @@ - - diff --git a/src/Frontend/src/composables/autoRefresh.ts b/src/Frontend/src/composables/autoRefresh.ts index 1131aed83..7bdca8d8a 100644 --- a/src/Frontend/src/composables/autoRefresh.ts +++ b/src/Frontend/src/composables/autoRefresh.ts @@ -1,5 +1,11 @@ -export default function useAutoRefresh(refreshAction: () => Promise, timeout: number) { +/** + * Enables refresh functionality, either auto or manual + * @param refreshAction The action to perform (by default) when refreshing + * @param defaultTimeout The time between refreshes in ms or null if no auto-refresh is desired + */ +export default function useAutoRefresh(refreshAction: () => Promise, defaultTimeout: number | null, startImmediately = true) { let refreshInterval: number | null = null; + const timeout = { value: defaultTimeout }; function stopTimer() { if (refreshInterval !== null) { @@ -9,10 +15,12 @@ export default function useAutoRefresh(refreshAction: () => Promise, timeo } function startTimer() { + if (timeout.value === null) return; + stopTimer(); refreshInterval = window?.setTimeout(() => { executeAndResetTimer(); - }, timeout); + }, timeout.value as number); } async function executeAndResetTimer(overrideAction?: () => Promise) { @@ -24,7 +32,20 @@ export default function useAutoRefresh(refreshAction: () => Promise, timeo } } + /** + * Updates the timeout interval between refreshes + * @param updatedTimeout The new time between refreshes in ms or null if no auto-refresh is desired + */ + async function updateTimeout(updatedTimeout: number | null) { + timeout.value = updatedTimeout; + await executeAndResetTimer(); + } + + // eslint-disable-next-line promise/catch-or-return,promise/prefer-await-to-then,promise/valid-params + if (startImmediately) executeAndResetTimer().then(); + return { executeAndResetTimer, + updateTimeout, }; } diff --git a/src/Frontend/src/resources/Message.ts b/src/Frontend/src/resources/Message.ts index a955a89b6..977497bbb 100644 --- a/src/Frontend/src/resources/Message.ts +++ b/src/Frontend/src/resources/Message.ts @@ -15,7 +15,7 @@ export default interface Message { is_system_message: boolean; conversation_id: string; headers: Header[]; - status: string; + status: MessageStatus; message_intent: string; body_url: string; body_size: number; @@ -27,3 +27,12 @@ export interface ExtendedMessage extends Message { headersNotFound: boolean; messageBodyNotFound: boolean; } + +export enum MessageStatus { + Failed = "failed", + RepeatedFailure = "repeatedFailure", + Successful = "successful", + ResolvedSuccessfully = "resolvedSuccessfully", + ArchivedFailure = "archivedFailure", + RetryIssued = "retryIssued", +} diff --git a/src/Frontend/src/router/config.ts b/src/Frontend/src/router/config.ts index 300c7a186..744f9c3df 100644 --- a/src/Frontend/src/router/config.ts +++ b/src/Frontend/src/router/config.ts @@ -8,13 +8,14 @@ import routeLinks from "@/router/routeLinks"; import CustomChecksView from "@/views/CustomChecksView.vue"; import HeartbeatsView from "@/views/HeartbeatsView.vue"; import ThroughputReportView from "@/views/ThroughputReportView.vue"; +import AuditView from "@/views/AuditView.vue"; export interface RouteItem { path: string; alias?: string; redirect?: string; title: string; - component: RouteComponent | (() => Promise); + component?: RouteComponent | (() => Promise); children?: RouteItem[]; } @@ -52,6 +53,11 @@ const config: RouteItem[] = [ }, ], }, + { + path: routeLinks.messages.root, + component: AuditView, + title: "All Messages", + }, { path: routeLinks.failedMessage.root, component: FailedMessagesView, @@ -96,7 +102,7 @@ const config: RouteItem[] = [ { path: routeLinks.failedMessage.message.template, title: "Message", - component: () => import("@/components/failedmessages/MessageRedirectForBackwardsCompatibility.vue"), + redirect: routeLinks.messages.message.template, }, ], }, diff --git a/src/Frontend/src/stores/AuditStore.ts b/src/Frontend/src/stores/AuditStore.ts new file mode 100644 index 000000000..9b2800416 --- /dev/null +++ b/src/Frontend/src/stores/AuditStore.ts @@ -0,0 +1,76 @@ +import { useTypedFetchFromServiceControl } from "@/composables/serviceServiceControlUrls"; +import { acceptHMRUpdate, defineStore } from "pinia"; +import { ref, watch } from "vue"; +import useAutoRefresh from "@/composables/autoRefresh"; +import type { SortInfo } from "@/components/SortInfo"; +import Message from "@/resources/Message"; + +export enum ColumnNames { + Status = "status", + MessageId = "messageId", + MessageType = "messageType", + TimeSent = "timeSent", + ProcessingTime = "processingTime", +} + +const columnSortings = new Map([ + [ColumnNames.Status, "status"], + [ColumnNames.MessageId, "id"], + [ColumnNames.MessageType, "message_type"], + [ColumnNames.TimeSent, "time_sent"], + [ColumnNames.ProcessingTime, "processing_time"], +]); + +export const useAuditStore = defineStore("AuditStore", () => { + const sortByInstances = ref({ + property: ColumnNames.TimeSent, + isAscending: false, + }); + + const messageFilterString = ref(""); + const itemsPerPage = ref(35); + const selectedPage = ref(1); + const totalCount = ref(0); + const messages = ref([]); + // const filteredMessages = computed(() => sortedMessages.value.filter((message) => !messageFilterString.value || message.id.toLowerCase().includes(messageFilterString.value.toLowerCase()))); + watch(messageFilterString, (newValue) => { + setMessageFilterString(newValue); + }); + + const dataRetriever = useAutoRefresh(async () => { + try { + const [response, data] = await useTypedFetchFromServiceControl( + `messages/?include_system_messages=false&per_page=${itemsPerPage.value}&page=${selectedPage.value}&sort=${columnSortings.get(sortByInstances.value.property)}&direction=${sortByInstances.value.isAscending ? "asc" : "desc"}` + ); + totalCount.value = parseInt(response.headers.get("total-count") ?? "0"); + messages.value = data; + } catch (e) { + messages.value = []; + throw e; + } + }, null); + + const refresh = dataRetriever.executeAndResetTimer; + watch([itemsPerPage, selectedPage, sortByInstances], () => refresh()); + + function setMessageFilterString(filter: string) { + messageFilterString.value = filter; + } + + return { + refresh, + updateRefreshTimer: dataRetriever.updateTimeout, + sortByInstances, + messages, + messageFilterString, + itemsPerPage, + selectedPage, + totalCount, + }; +}); + +if (import.meta.hot) { + import.meta.hot.accept(acceptHMRUpdate(useAuditStore, import.meta.hot)); +} + +export type AuditStore = ReturnType; diff --git a/src/Frontend/src/stores/CustomChecksStore.ts b/src/Frontend/src/stores/CustomChecksStore.ts index 514e94c46..d31a0bd29 100644 --- a/src/Frontend/src/stores/CustomChecksStore.ts +++ b/src/Frontend/src/stores/CustomChecksStore.ts @@ -37,8 +37,6 @@ export const useCustomChecksStore = defineStore("CustomChecksStore", () => { }); } - dataRetriever.executeAndResetTimer(); - return { dismissCustomCheck, pageNumber, diff --git a/src/Frontend/src/stores/HeartbeatsStore.ts b/src/Frontend/src/stores/HeartbeatsStore.ts index 77bb48006..4c0a00152 100644 --- a/src/Frontend/src/stores/HeartbeatsStore.ts +++ b/src/Frontend/src/stores/HeartbeatsStore.ts @@ -170,9 +170,6 @@ export const useHeartbeatsStore = defineStore("HeartbeatsStore", () => { const refresh = dataRetriever.executeAndResetTimer; - // eslint-disable-next-line promise/catch-or-return,promise/prefer-await-to-then,promise/valid-params - refresh().then(); - return { refresh, defaultTrackingInstancesValue, diff --git a/src/Frontend/src/stores/ThroughputStore.ts b/src/Frontend/src/stores/ThroughputStore.ts index bc04e49c4..e13dfbd07 100644 --- a/src/Frontend/src/stores/ThroughputStore.ts +++ b/src/Frontend/src/stores/ThroughputStore.ts @@ -101,8 +101,6 @@ export const useThroughputStore = defineStore("ThroughputStore", () => { } }); - refresh(); - return { testResults, refresh, diff --git a/src/Frontend/src/views/AuditView.vue b/src/Frontend/src/views/AuditView.vue new file mode 100644 index 000000000..70eccf222 --- /dev/null +++ b/src/Frontend/src/views/AuditView.vue @@ -0,0 +1,37 @@ + + + + +