From 4a29fc9dda891c9b41f419637cd9f22bce74f2ef Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Tue, 17 Dec 2024 10:25:05 -0500 Subject: [PATCH] fix(web): display error message in sidebar when api is offline (#984) * fix(web): display error message in sidebar when api is offline * refactor(web): move offline error derivation to UnraidApiStore * feat(web): display error in upc when api is offline --- web/components/Notifications/List.vue | 8 +-- web/helpers/create-apollo-client.ts | 2 +- web/store/unraidApi.ts | 83 ++++++++++++++++----------- 3 files changed, 52 insertions(+), 41 deletions(-) diff --git a/web/components/Notifications/List.vue b/web/components/Notifications/List.vue index 013a7b162..06ee62115 100644 --- a/web/components/Notifications/List.vue +++ b/web/components/Notifications/List.vue @@ -4,6 +4,7 @@ import { useQuery } from '@vue/apollo-composable'; import { vInfiniteScroll } from '@vueuse/components'; import { useFragment } from '~/composables/gql/fragment-masking'; import type { Importance, NotificationType } from '~/composables/gql/graphql'; +import { useUnraidApiStore } from '~/store/unraidApi'; import { getNotifications, NOTIFICATION_FRAGMENT } from './graphql/notification.query'; /** @@ -28,6 +29,7 @@ watch(props, () => { canLoadMore.value = true; }); +const { offlineError } = useUnraidApiStore(); const { result, error, loading, fetchMore, refetch } = useQuery(getNotifications, () => ({ filter: { offset: 0, @@ -37,10 +39,6 @@ const { result, error, loading, fetchMore, refetch } = useQuery(getNotifications }, })); -watch(error, (newVal) => { - console.log('[getNotifications] error:', newVal); -}); - const notifications = computed(() => { if (!result.value?.notifications.list) return []; const list = useFragment(NOTIFICATION_FRAGMENT, result.value?.notifications.list); @@ -85,7 +83,7 @@ async function onLoadMore() { - +
{{ `No ${props.importance?.toLowerCase() ?? ''} notifications to see here!` }} diff --git a/web/helpers/create-apollo-client.ts b/web/helpers/create-apollo-client.ts index 23db45bde..a1a5b12be 100644 --- a/web/helpers/create-apollo-client.ts +++ b/web/helpers/create-apollo-client.ts @@ -45,7 +45,7 @@ const errorLink = onError(({ graphQLErrors, networkError }: any) => { console.error('[GraphQL error]', error); const errorMsg = error.error?.message ?? error.message; if (errorMsg?.includes('offline')) { - // @todo restart the api + // @todo restart the api, but make sure not to trigger infinite loop } return error.message; }); diff --git a/web/store/unraidApi.ts b/web/store/unraidApi.ts index 3ef6b05f6..7d486faa3 100644 --- a/web/store/unraidApi.ts +++ b/web/store/unraidApi.ts @@ -1,17 +1,12 @@ -import { - type ApolloClient as ApolloClientType, - type NormalizedCacheObject, -} from "@apollo/client"; -import { ArrowPathIcon } from "@heroicons/vue/24/solid"; +import { type ApolloClient as ApolloClientType, type NormalizedCacheObject } from '@apollo/client'; +import { ArrowPathIcon } from '@heroicons/vue/24/solid'; +import { WebguiUnraidApiCommand } from '~/composables/services/webgui'; +import { client } from '~/helpers/create-apollo-client'; +import { useErrorsStore } from '~/store/errors'; +import { useServerStore } from '~/store/server'; +import type { UserProfileLink } from '~/types/userProfile'; // import { logErrorMessages } from '@vue/apollo-util'; -import { defineStore, createPinia, setActivePinia } from "pinia"; -import type { UserProfileLink } from "~/types/userProfile"; - -import { WebguiUnraidApiCommand } from "~/composables/services/webgui"; -import { useErrorsStore } from "~/store/errors"; -import { useServerStore } from "~/store/server"; - -import { client } from "~/helpers/create-apollo-client"; +import { createPinia, defineStore, setActivePinia } from 'pinia'; /** * @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components @@ -19,33 +14,50 @@ import { client } from "~/helpers/create-apollo-client"; */ setActivePinia(createPinia()); -export const useUnraidApiStore = defineStore("unraidApi", () => { +export const useUnraidApiStore = defineStore('unraidApi', () => { const errorsStore = useErrorsStore(); const serverStore = useServerStore(); - const unraidApiClient = ref | null>( - client - ); + const unraidApiClient = ref | null>(client); // const unraidApiErrors = ref([]); - const unraidApiStatus = ref< - "connecting" | "offline" | "online" | "restarting" - >("offline"); + const unraidApiStatus = ref<'connecting' | 'offline' | 'online' | 'restarting'>('offline'); const prioritizeCorsError = ref(false); // Ensures we don't overwrite this specific error message with a non-descriptive network error message + const offlineError = computed(() => { + if (unraidApiStatus.value === 'offline') { + return new Error('The Unraid API is currently offline.'); + } + }); + // maintains an error in global store while api is offline + watch( + offlineError, + (error) => { + const errorId = 'unraidApiOffline'; + if (error) { + errorsStore.setError({ + heading: 'Warning: API is offline!', + message: error.message, + ref: errorId, + level: 'warning', + type: 'unraidApiState', + }); + } else { + errorsStore.removeErrorByRef(errorId); + } + }, + { immediate: true } + ); + const unraidApiRestartAction = computed((): UserProfileLink | undefined => { const { connectPluginInstalled, stateDataError } = serverStore; - if ( - unraidApiStatus.value !== "offline" || - !connectPluginInstalled || - stateDataError - ) { + if (unraidApiStatus.value !== 'offline' || !connectPluginInstalled || stateDataError) { return undefined; } return { click: () => restartUnraidApiClient(), emphasize: true, icon: ArrowPathIcon, - text: "Restart unraid-api", + text: 'Restart unraid-api', }; }); @@ -62,32 +74,32 @@ export const useUnraidApiStore = defineStore("unraidApi", () => { // (wsLink.value as any).subscriptionClient.close(); // needed if we start using subscriptions } unraidApiClient.value = null; - unraidApiStatus.value = "offline"; + unraidApiStatus.value = 'offline'; }; /** * Can both start and restart the unraid-api depending on it's current status */ const restartUnraidApiClient = async () => { - const command = unraidApiStatus.value === "offline" ? "start" : "restart"; - unraidApiStatus.value = "restarting"; + const command = unraidApiStatus.value === 'offline' ? 'start' : 'restart'; + unraidApiStatus.value = 'restarting'; try { await WebguiUnraidApiCommand({ csrf_token: serverStore.csrf, command, }); } catch (error) { - let errorMessage = "Unknown error"; - if (typeof error === "string") { + let errorMessage = 'Unknown error'; + if (typeof error === 'string') { errorMessage = error.toUpperCase(); } else if (error instanceof Error) { errorMessage = error.message; } errorsStore.setError({ - heading: "Error: unraid-api restart", + heading: 'Error: unraid-api restart', message: errorMessage, - level: "error", - ref: "restartUnraidApiClient", - type: "request", + level: 'error', + ref: 'restartUnraidApiClient', + type: 'request', }); } }; @@ -95,6 +107,7 @@ export const useUnraidApiStore = defineStore("unraidApi", () => { return { unraidApiClient, unraidApiStatus, + offlineError, prioritizeCorsError, unraidApiRestartAction, closeUnraidApiClient,