mirror of
https://github.com/unraid/api.git
synced 2025-12-30 21:19:49 -06:00
refactor(web): instantiate apollo client statically instead of dynamically
This commit is contained in:
@@ -6,10 +6,9 @@ import {
|
||||
ExclamationTriangleIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import type { NotificationFragmentFragment } from '~/composables/gql/graphql';
|
||||
|
||||
import type { NotificationItemProps } from '~/types/ui/notification';
|
||||
|
||||
const props = defineProps<NotificationItemProps>();
|
||||
const props = defineProps<NotificationFragmentFragment>();
|
||||
|
||||
const icon = computed<{ component: Component, color: string } | null>(() => {
|
||||
switch (props.importance) {
|
||||
|
||||
@@ -9,52 +9,39 @@ import {
|
||||
SheetTrigger,
|
||||
} from "@/components/shadcn/sheet";
|
||||
|
||||
import type { NotificationItemProps } from "~/types/ui/notification";
|
||||
import { useUnraidApiStore } from "~/store/unraidApi";
|
||||
import gql from "graphql-tag";
|
||||
import {
|
||||
getNotifications,
|
||||
NOTIFICATION_FRAGMENT,
|
||||
} from "./graphql/notification.query";
|
||||
import {
|
||||
NotificationType,
|
||||
} from "~/composables/gql/graphql";
|
||||
import { useFragment } from "~/composables/gql/fragment-masking";
|
||||
import { useQuery } from "@vue/apollo-composable";
|
||||
|
||||
const getNotifications = gql`
|
||||
query Notifications($filter: NotificationFilter!) {
|
||||
notifications {
|
||||
list(filter: $filter) {
|
||||
id
|
||||
title
|
||||
subject
|
||||
description
|
||||
importance
|
||||
link
|
||||
type
|
||||
timestamp
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
// const notifications = ref<NotificationFragmentFragment[]>([]);
|
||||
// watch(notifications, (newVal) => {
|
||||
// console.log("[notifications]", newVal);
|
||||
// });
|
||||
|
||||
const notifications = ref<NotificationItemProps[]>([]);
|
||||
watch(notifications, (newVal) => {
|
||||
console.log("[notifications]", newVal);
|
||||
const fetchType = ref<NotificationType>(NotificationType.Unread);
|
||||
const setFetchType = (type: NotificationType) => (fetchType.value = type);
|
||||
|
||||
const { result, error } = useQuery(getNotifications, {
|
||||
filter: {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
type: fetchType.value,
|
||||
},
|
||||
});
|
||||
|
||||
const fetchType = ref<"UNREAD" | "ARCHIVED">("UNREAD");
|
||||
const setFetchType = (type: "UNREAD" | "ARCHIVED") => (fetchType.value = type);
|
||||
const notifications = computed(() => {
|
||||
if (!result.value?.notifications.list) return [];
|
||||
return useFragment(NOTIFICATION_FRAGMENT, result.value?.notifications.list);
|
||||
});
|
||||
|
||||
const { unraidApiClient: maybeApi } = storeToRefs(useUnraidApiStore());
|
||||
|
||||
|
||||
watch(maybeApi, async (apiClient) => {
|
||||
if (apiClient) {
|
||||
const apiResponse = await apiClient.query({
|
||||
query: getNotifications,
|
||||
variables: {
|
||||
filter: {
|
||||
offset: 0,
|
||||
limit: 10,
|
||||
type: fetchType.value,
|
||||
},
|
||||
},
|
||||
});
|
||||
notifications.value = apiResponse.data.notifications.list;
|
||||
}
|
||||
watch(error, (newVal) => {
|
||||
console.log("[sidebar error]", newVal);
|
||||
});
|
||||
|
||||
const { teleportTarget, determineTeleportTarget } = useTeleport();
|
||||
@@ -81,14 +68,14 @@ const { teleportTarget, determineTeleportTarget } = useTeleport();
|
||||
<TabsTrigger
|
||||
class="text-[1rem] leading-[1.3rem]"
|
||||
value="unread"
|
||||
@click="setFetchType('UNREAD')"
|
||||
@click="setFetchType(NotificationType.Unread)"
|
||||
>
|
||||
Unread
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
class="text-[1rem] leading-[1.3rem]"
|
||||
value="archived"
|
||||
@="setFetchType('ARCHIVED')"
|
||||
@="setFetchType(NotificationType.Archive)"
|
||||
>
|
||||
Archived
|
||||
</TabsTrigger>
|
||||
@@ -97,7 +84,7 @@ const { teleportTarget, determineTeleportTarget } = useTeleport();
|
||||
<Button
|
||||
variant="link"
|
||||
size="sm"
|
||||
class="text-muted-foreground text-[1rem] leading-[1.3rem] p-0"
|
||||
class="text-muted-foreground text-sm p-0"
|
||||
>
|
||||
{{ `Archive All` }}
|
||||
</Button>
|
||||
@@ -119,7 +106,10 @@ const { teleportTarget, determineTeleportTarget } = useTeleport();
|
||||
|
||||
<TabsContent value="unread">
|
||||
<ScrollArea>
|
||||
<div class="divide-y divide-gray-200">
|
||||
<div
|
||||
v-if="notifications?.length > 0"
|
||||
class="divide-y divide-gray-200"
|
||||
>
|
||||
<NotificationsItem
|
||||
v-for="notification in notifications"
|
||||
:key="notification.id"
|
||||
@@ -139,4 +129,4 @@ const { teleportTarget, determineTeleportTarget } = useTeleport();
|
||||
</SheetFooter>
|
||||
</SheetContent>
|
||||
</Sheet>
|
||||
</template>
|
||||
</template>
|
||||
26
web/components/Notifications/graphql/notification.query.ts
Normal file
26
web/components/Notifications/graphql/notification.query.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { graphql } from "~/composables/gql/gql";
|
||||
|
||||
export const NOTIFICATION_FRAGMENT = graphql(/* GraphQL */ `
|
||||
fragment NotificationFragment on Notification {
|
||||
id
|
||||
title
|
||||
subject
|
||||
description
|
||||
importance
|
||||
link
|
||||
type
|
||||
timestamp
|
||||
}
|
||||
`);
|
||||
|
||||
export const getNotifications = graphql(/* GraphQL */ `
|
||||
query Notifications($filter: NotificationFilter!) {
|
||||
notifications {
|
||||
id
|
||||
list(filter: $filter) {
|
||||
...NotificationFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -128,7 +128,7 @@ onBeforeMount(() => {
|
||||
<div class="block w-2px h-24px bg-gamma" />
|
||||
|
||||
<!-- Keep the sidebar out of staging/prod builds, but easily accessible for development -->
|
||||
<!-- <NotificationsSidebar /> -->
|
||||
<NotificationsSidebar />
|
||||
|
||||
<OnClickOutside class="flex items-center justify-end h-full" :options="{ ignore: [clickOutsideIgnoreTarget] }" @trigger="outsideDropdown">
|
||||
<UpcDropdownTrigger ref="clickOutsideIgnoreTarget" :t="t" />
|
||||
|
||||
@@ -13,6 +13,8 @@ import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-
|
||||
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
||||
*/
|
||||
const documents = {
|
||||
"\n fragment NotificationFragment on Notification {\n id\n title\n subject\n description\n importance\n link\n type\n timestamp\n }\n": types.NotificationFragmentFragmentDoc,
|
||||
"\n query Notifications($filter: NotificationFilter!) {\n notifications {\n id\n list(filter: $filter) {\n ...NotificationFragment\n }\n }\n }\n": types.NotificationsDocument,
|
||||
"\n mutation ConnectSignIn($input: ConnectSignInInput!) {\n connectSignIn(input: $input)\n }\n": types.ConnectSignInDocument,
|
||||
"\n mutation SignOut {\n connectSignOut\n }\n": types.SignOutDocument,
|
||||
"\n fragment PartialCloud on Cloud {\n error\n apiKey {\n valid\n error\n }\n cloud {\n status\n error\n }\n minigraphql {\n status\n error\n }\n relay {\n status\n error\n }\n }\n": types.PartialCloudFragmentDoc,
|
||||
@@ -37,6 +39,14 @@ const documents = {
|
||||
*/
|
||||
export function graphql(source: string): unknown;
|
||||
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment NotificationFragment on Notification {\n id\n title\n subject\n description\n importance\n link\n type\n timestamp\n }\n"): (typeof documents)["\n fragment NotificationFragment on Notification {\n id\n title\n subject\n description\n importance\n link\n type\n timestamp\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query Notifications($filter: NotificationFilter!) {\n notifications {\n id\n list(filter: $filter) {\n ...NotificationFragment\n }\n }\n }\n"): (typeof documents)["\n query Notifications($filter: NotificationFilter!) {\n notifications {\n id\n list(filter: $filter) {\n ...NotificationFragment\n }\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
|
||||
/* eslint-disable */
|
||||
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
@@ -22,10 +22,27 @@ export type Scalars = {
|
||||
Long: { input: number; output: number; }
|
||||
/** A field whose value is a valid TCP port within the range of 0 to 65535: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_ports */
|
||||
Port: { input: number; output: number; }
|
||||
/** A field whose value conforms to the standard URL format as specified in RFC3986: https://www.ietf.org/rfc/rfc3986.txt. */
|
||||
URL: { input: URL; output: URL; }
|
||||
/** A field whose value is a generic Universally Unique Identifier: https://en.wikipedia.org/wiki/Universally_unique_identifier. */
|
||||
UUID: { input: string; output: string; }
|
||||
};
|
||||
|
||||
export type AccessUrl = {
|
||||
__typename?: 'AccessUrl';
|
||||
ipv4?: Maybe<Scalars['URL']['output']>;
|
||||
ipv6?: Maybe<Scalars['URL']['output']>;
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
type: URL_TYPE;
|
||||
};
|
||||
|
||||
export type AccessUrlInput = {
|
||||
ipv4?: InputMaybe<Scalars['URL']['input']>;
|
||||
ipv6?: InputMaybe<Scalars['URL']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
type: URL_TYPE;
|
||||
};
|
||||
|
||||
export type AllowedOriginInput = {
|
||||
origins: Array<Scalars['String']['input']>;
|
||||
};
|
||||
@@ -45,7 +62,7 @@ export type ApiKeyResponse = {
|
||||
valid: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type ArrayType = {
|
||||
export type ArrayType = Node & {
|
||||
__typename?: 'Array';
|
||||
/** Current boot disk */
|
||||
boot?: Maybe<ArrayDisk>;
|
||||
@@ -55,6 +72,7 @@ export type ArrayType = {
|
||||
capacity: ArrayCapacity;
|
||||
/** Data disks in the current array */
|
||||
disks: Array<ArrayDisk>;
|
||||
id: Scalars['ID']['output'];
|
||||
/** Parity disks in the current array */
|
||||
parities: Array<ArrayDisk>;
|
||||
/** Array state after this query/mutation */
|
||||
@@ -235,20 +253,26 @@ export type CloudResponse = {
|
||||
status: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Config = {
|
||||
export type Config = Node & {
|
||||
__typename?: 'Config';
|
||||
error?: Maybe<ConfigErrorState>;
|
||||
id: Scalars['ID']['output'];
|
||||
valid?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export enum ConfigErrorState {
|
||||
Ineligible = 'INELIGIBLE',
|
||||
Invalid = 'INVALID',
|
||||
NoKeyServer = 'NO_KEY_SERVER',
|
||||
UnknownError = 'UNKNOWN_ERROR',
|
||||
Withdrawn = 'WITHDRAWN'
|
||||
}
|
||||
|
||||
export type Connect = Node & {
|
||||
__typename?: 'Connect';
|
||||
dynamicRemoteAccess: DynamicRemoteAccessStatus;
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type ConnectSignInInput = {
|
||||
accessToken?: InputMaybe<Scalars['String']['input']>;
|
||||
apiKey: Scalars['String']['input'];
|
||||
@@ -363,6 +387,7 @@ export type Display = {
|
||||
dashapps?: Maybe<Scalars['String']['output']>;
|
||||
date?: Maybe<Scalars['String']['output']>;
|
||||
hot?: Maybe<Scalars['Int']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
locale?: Maybe<Scalars['String']['output']>;
|
||||
max?: Maybe<Scalars['Int']['output']>;
|
||||
number?: Maybe<Scalars['String']['output']>;
|
||||
@@ -379,6 +404,13 @@ export type Display = {
|
||||
wwn?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type Docker = Node & {
|
||||
__typename?: 'Docker';
|
||||
containers?: Maybe<Array<DockerContainer>>;
|
||||
id: Scalars['ID']['output'];
|
||||
networks?: Maybe<Array<DockerNetwork>>;
|
||||
};
|
||||
|
||||
export type DockerContainer = {
|
||||
__typename?: 'DockerContainer';
|
||||
autoStart: Scalars['Boolean']['output'];
|
||||
@@ -418,6 +450,24 @@ export type DockerNetwork = {
|
||||
scope?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type DynamicRemoteAccessStatus = {
|
||||
__typename?: 'DynamicRemoteAccessStatus';
|
||||
enabledType: DynamicRemoteAccessType;
|
||||
error?: Maybe<Scalars['String']['output']>;
|
||||
runningType: DynamicRemoteAccessType;
|
||||
};
|
||||
|
||||
export enum DynamicRemoteAccessType {
|
||||
Disabled = 'DISABLED',
|
||||
Static = 'STATIC',
|
||||
Upnp = 'UPNP'
|
||||
}
|
||||
|
||||
export type EnableDynamicRemoteAccessInput = {
|
||||
enabled: Scalars['Boolean']['input'];
|
||||
url: AccessUrlInput;
|
||||
};
|
||||
|
||||
export type Flash = {
|
||||
__typename?: 'Flash';
|
||||
guid?: Maybe<Scalars['String']['output']>;
|
||||
@@ -442,7 +492,7 @@ export enum Importance {
|
||||
Warning = 'WARNING'
|
||||
}
|
||||
|
||||
export type Info = {
|
||||
export type Info = Node & {
|
||||
__typename?: 'Info';
|
||||
/** Count of docker containers */
|
||||
apps?: Maybe<InfoApps>;
|
||||
@@ -450,11 +500,13 @@ export type Info = {
|
||||
cpu?: Maybe<InfoCpu>;
|
||||
devices?: Maybe<Devices>;
|
||||
display?: Maybe<Display>;
|
||||
id: Scalars['ID']['output'];
|
||||
/** Machine ID */
|
||||
machineId?: Maybe<Scalars['ID']['output']>;
|
||||
memory?: Maybe<InfoMemory>;
|
||||
os?: Maybe<Os>;
|
||||
system?: Maybe<System>;
|
||||
time: Scalars['DateTime']['output'];
|
||||
versions?: Maybe<Versions>;
|
||||
};
|
||||
|
||||
@@ -574,13 +626,20 @@ export type Mutation = {
|
||||
addDiskToArray?: Maybe<ArrayType>;
|
||||
/** Add a new user */
|
||||
addUser?: Maybe<User>;
|
||||
archiveAll: NotificationOverview;
|
||||
/** Marks a notification as archived. */
|
||||
archiveNotification: NotificationOverview;
|
||||
archiveNotifications: NotificationOverview;
|
||||
/** Cancel parity check */
|
||||
cancelParityCheck?: Maybe<Scalars['JSON']['output']>;
|
||||
clearArrayDiskStatistics?: Maybe<Scalars['JSON']['output']>;
|
||||
connectSignIn: Scalars['Boolean']['output'];
|
||||
connectSignOut: Scalars['Boolean']['output'];
|
||||
createNotification: Notification;
|
||||
deleteNotification: NotificationOverview;
|
||||
/** Delete a user */
|
||||
deleteUser?: Maybe<User>;
|
||||
enableDynamicRemoteAccess: Scalars['Boolean']['output'];
|
||||
/** Get an existing API key */
|
||||
getApiKey?: Maybe<ApiKey>;
|
||||
login?: Maybe<Scalars['String']['output']>;
|
||||
@@ -588,11 +647,12 @@ export type Mutation = {
|
||||
/** Pause parity check */
|
||||
pauseParityCheck?: Maybe<Scalars['JSON']['output']>;
|
||||
reboot?: Maybe<Scalars['String']['output']>;
|
||||
/** Reads each notification to recompute & update the overview. */
|
||||
recalculateOverview: NotificationOverview;
|
||||
/** Remove existing disk from array. NOTE: The array must be stopped before running this otherwise it'll throw an error. */
|
||||
removeDiskFromArray?: Maybe<ArrayType>;
|
||||
/** Resume parity check */
|
||||
resumeParityCheck?: Maybe<Scalars['JSON']['output']>;
|
||||
sendNotification?: Maybe<Notification>;
|
||||
setAdditionalAllowedOrigins: Array<Scalars['String']['output']>;
|
||||
setupRemoteAccess: Scalars['Boolean']['output'];
|
||||
shutdown?: Maybe<Scalars['String']['output']>;
|
||||
@@ -602,7 +662,11 @@ export type Mutation = {
|
||||
startParityCheck?: Maybe<Scalars['JSON']['output']>;
|
||||
/** Stop array */
|
||||
stopArray?: Maybe<ArrayType>;
|
||||
unarchiveAll: NotificationOverview;
|
||||
unarchiveNotifications: NotificationOverview;
|
||||
unmountArrayDisk?: Maybe<Disk>;
|
||||
/** Marks a notification as unread. */
|
||||
unreadNotification: NotificationOverview;
|
||||
/** Update an existing API key */
|
||||
updateApikey?: Maybe<ApiKey>;
|
||||
};
|
||||
@@ -624,6 +688,21 @@ export type MutationaddUserArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationarchiveAllArgs = {
|
||||
importance?: InputMaybe<Importance>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationarchiveNotificationArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationarchiveNotificationsArgs = {
|
||||
ids?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationclearArrayDiskStatisticsArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
@@ -634,11 +713,27 @@ export type MutationconnectSignInArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationcreateNotificationArgs = {
|
||||
input: NotificationData;
|
||||
};
|
||||
|
||||
|
||||
export type MutationdeleteNotificationArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
type: NotificationType;
|
||||
};
|
||||
|
||||
|
||||
export type MutationdeleteUserArgs = {
|
||||
input: deleteUserInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationenableDynamicRemoteAccessArgs = {
|
||||
input: EnableDynamicRemoteAccessInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationgetApiKeyArgs = {
|
||||
input?: InputMaybe<authenticateInput>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -661,11 +756,6 @@ export type MutationremoveDiskFromArrayArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationsendNotificationArgs = {
|
||||
notification: NotificationInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationsetAdditionalAllowedOriginsArgs = {
|
||||
input: AllowedOriginInput;
|
||||
};
|
||||
@@ -681,20 +771,37 @@ export type MutationstartParityCheckArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationunarchiveAllArgs = {
|
||||
importance?: InputMaybe<Importance>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationunarchiveNotificationsArgs = {
|
||||
ids?: InputMaybe<Array<Scalars['String']['input']>>;
|
||||
};
|
||||
|
||||
|
||||
export type MutationunmountArrayDiskArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationunreadNotificationArgs = {
|
||||
id: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
|
||||
export type MutationupdateApikeyArgs = {
|
||||
input?: InputMaybe<updateApikeyInput>;
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type Network = {
|
||||
export type Network = Node & {
|
||||
__typename?: 'Network';
|
||||
accessUrls?: Maybe<Array<AccessUrl>>;
|
||||
carrierChanges?: Maybe<Scalars['String']['output']>;
|
||||
duplex?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
iface?: Maybe<Scalars['String']['output']>;
|
||||
ifaceName?: Maybe<Scalars['String']['output']>;
|
||||
internal?: Maybe<Scalars['String']['output']>;
|
||||
@@ -707,19 +814,40 @@ export type Network = {
|
||||
type?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Notification = {
|
||||
export type Node = {
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type Notification = Node & {
|
||||
__typename?: 'Notification';
|
||||
description: Scalars['String']['output'];
|
||||
id: Scalars['ID']['output'];
|
||||
importance: Importance;
|
||||
link?: Maybe<Scalars['String']['output']>;
|
||||
subject: Scalars['String']['output'];
|
||||
/** ISO Timestamp for when the notification occurred */
|
||||
/** ISO Timestamp for when the notification occurred */
|
||||
timestamp?: Maybe<Scalars['String']['output']>;
|
||||
/** Also known as 'event' */
|
||||
title: Scalars['String']['output'];
|
||||
type: NotificationType;
|
||||
};
|
||||
|
||||
export type NotificationCounts = {
|
||||
__typename?: 'NotificationCounts';
|
||||
alert: Scalars['Int']['output'];
|
||||
info: Scalars['Int']['output'];
|
||||
total: Scalars['Int']['output'];
|
||||
warning: Scalars['Int']['output'];
|
||||
};
|
||||
|
||||
export type NotificationData = {
|
||||
description: Scalars['String']['input'];
|
||||
importance: Importance;
|
||||
link?: InputMaybe<Scalars['String']['input']>;
|
||||
subject: Scalars['String']['input'];
|
||||
title: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type NotificationFilter = {
|
||||
importance?: InputMaybe<Importance>;
|
||||
limit: Scalars['Int']['input'];
|
||||
@@ -727,23 +855,30 @@ export type NotificationFilter = {
|
||||
type?: InputMaybe<NotificationType>;
|
||||
};
|
||||
|
||||
export type NotificationInput = {
|
||||
description?: InputMaybe<Scalars['String']['input']>;
|
||||
id: Scalars['ID']['input'];
|
||||
importance: Importance;
|
||||
link?: InputMaybe<Scalars['String']['input']>;
|
||||
subject: Scalars['String']['input'];
|
||||
timestamp?: InputMaybe<Scalars['String']['input']>;
|
||||
title: Scalars['String']['input'];
|
||||
type: NotificationType;
|
||||
export type NotificationOverview = {
|
||||
__typename?: 'NotificationOverview';
|
||||
archive: NotificationCounts;
|
||||
unread: NotificationCounts;
|
||||
};
|
||||
|
||||
export enum NotificationType {
|
||||
Archived = 'ARCHIVED',
|
||||
Restored = 'RESTORED',
|
||||
Archive = 'ARCHIVE',
|
||||
Unread = 'UNREAD'
|
||||
}
|
||||
|
||||
export type Notifications = Node & {
|
||||
__typename?: 'Notifications';
|
||||
id: Scalars['ID']['output'];
|
||||
list: Array<Notification>;
|
||||
/** A cached overview of the notifications in the system & their severity. */
|
||||
overview: NotificationOverview;
|
||||
};
|
||||
|
||||
|
||||
export type NotificationslistArgs = {
|
||||
filter: NotificationFilter;
|
||||
};
|
||||
|
||||
export type Os = {
|
||||
__typename?: 'Os';
|
||||
arch?: Maybe<Scalars['String']['output']>;
|
||||
@@ -867,11 +1002,13 @@ export type Query = {
|
||||
array: ArrayType;
|
||||
cloud?: Maybe<Cloud>;
|
||||
config: Config;
|
||||
connect: Connect;
|
||||
/** Single disk */
|
||||
disk?: Maybe<Disk>;
|
||||
/** Mulitiple disks */
|
||||
disks: Array<Maybe<Disk>>;
|
||||
display?: Maybe<Display>;
|
||||
docker: Docker;
|
||||
/** All Docker containers */
|
||||
dockerContainers: Array<DockerContainer>;
|
||||
/** Docker network */
|
||||
@@ -883,7 +1020,8 @@ export type Query = {
|
||||
info?: Maybe<Info>;
|
||||
/** Current user account */
|
||||
me?: Maybe<Me>;
|
||||
notifications: Array<Notification>;
|
||||
network?: Maybe<Network>;
|
||||
notifications: Notifications;
|
||||
online?: Maybe<Scalars['Boolean']['output']>;
|
||||
owner?: Maybe<Owner>;
|
||||
parityHistory?: Maybe<Array<Maybe<ParityCheck>>>;
|
||||
@@ -891,6 +1029,7 @@ export type Query = {
|
||||
remoteAccess: RemoteAccess;
|
||||
server?: Maybe<Server>;
|
||||
servers: Array<Server>;
|
||||
services: Array<Service>;
|
||||
/** Network Shares */
|
||||
shares?: Maybe<Array<Maybe<Share>>>;
|
||||
unassignedDevices?: Maybe<Array<Maybe<UnassignedDevice>>>;
|
||||
@@ -924,11 +1063,6 @@ export type QuerydockerNetworksArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type QuerynotificationsArgs = {
|
||||
filter: NotificationFilter;
|
||||
};
|
||||
|
||||
|
||||
export type QueryuserArgs = {
|
||||
id: Scalars['ID']['input'];
|
||||
};
|
||||
@@ -1022,8 +1156,9 @@ export enum ServerStatus {
|
||||
Online = 'online'
|
||||
}
|
||||
|
||||
export type Service = {
|
||||
export type Service = Node & {
|
||||
__typename?: 'Service';
|
||||
id: Scalars['ID']['output'];
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
online?: Maybe<Scalars['Boolean']['output']>;
|
||||
uptime?: Maybe<Uptime>;
|
||||
@@ -1077,6 +1212,7 @@ export type Subscription = {
|
||||
info: Info;
|
||||
me?: Maybe<Me>;
|
||||
notificationAdded: Notification;
|
||||
notificationsOverview: NotificationOverview;
|
||||
online: Scalars['Boolean']['output'];
|
||||
owner: Owner;
|
||||
parityHistory: ParityCheck;
|
||||
@@ -1137,6 +1273,15 @@ export enum Theme {
|
||||
White = 'white'
|
||||
}
|
||||
|
||||
export enum URL_TYPE {
|
||||
Default = 'DEFAULT',
|
||||
Lan = 'LAN',
|
||||
Mdns = 'MDNS',
|
||||
Other = 'OTHER',
|
||||
Wan = 'WAN',
|
||||
Wireguard = 'WIREGUARD'
|
||||
}
|
||||
|
||||
export type UnassignedDevice = {
|
||||
__typename?: 'UnassignedDevice';
|
||||
devlinks?: Maybe<Scalars['String']['output']>;
|
||||
@@ -1223,7 +1368,7 @@ export type UserAccount = {
|
||||
roles: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Vars = {
|
||||
export type Vars = Node & {
|
||||
__typename?: 'Vars';
|
||||
bindMgt?: Maybe<Scalars['Boolean']['output']>;
|
||||
cacheNumDevices?: Maybe<Scalars['Int']['output']>;
|
||||
@@ -1257,6 +1402,7 @@ export type Vars = {
|
||||
fuseRememberDefault?: Maybe<Scalars['String']['output']>;
|
||||
fuseRememberStatus?: Maybe<Scalars['String']['output']>;
|
||||
hideDotFiles?: Maybe<Scalars['Boolean']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
joinStatus?: Maybe<Scalars['String']['output']>;
|
||||
localMaster?: Maybe<Scalars['Boolean']['output']>;
|
||||
localTld?: Maybe<Scalars['String']['output']>;
|
||||
@@ -1510,6 +1656,18 @@ export type usersInput = {
|
||||
slim?: InputMaybe<Scalars['Boolean']['input']>;
|
||||
};
|
||||
|
||||
export type NotificationFragmentFragment = { __typename?: 'Notification', id: string, title: string, subject: string, description: string, importance: Importance, link?: string | null, type: NotificationType, timestamp?: string | null } & { ' $fragmentName'?: 'NotificationFragmentFragment' };
|
||||
|
||||
export type NotificationsQueryVariables = Exact<{
|
||||
filter: NotificationFilter;
|
||||
}>;
|
||||
|
||||
|
||||
export type NotificationsQuery = { __typename?: 'Query', notifications: { __typename?: 'Notifications', id: string, list: Array<(
|
||||
{ __typename?: 'Notification' }
|
||||
& { ' $fragmentRefs'?: { 'NotificationFragmentFragment': NotificationFragmentFragment } }
|
||||
)> } };
|
||||
|
||||
export type ConnectSignInMutationVariables = Exact<{
|
||||
input: ConnectSignInInput;
|
||||
}>;
|
||||
@@ -1556,7 +1714,9 @@ export type setupRemoteAccessMutationVariables = Exact<{
|
||||
|
||||
export type setupRemoteAccessMutation = { __typename?: 'Mutation', setupRemoteAccess: boolean };
|
||||
|
||||
export const NotificationFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Notification"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}}]}}]} as unknown as DocumentNode<NotificationFragmentFragment, unknown>;
|
||||
export const PartialCloudFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialCloud"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"minigraphql"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode<PartialCloudFragment, unknown>;
|
||||
export const NotificationsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"Notifications"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"filter"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationFilter"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"notifications"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"list"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"filter"},"value":{"kind":"Variable","name":{"kind":"Name","value":"filter"}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"NotificationFragment"}}]}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"NotificationFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Notification"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"id"}},{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"timestamp"}}]}}]} as unknown as DocumentNode<NotificationsQuery, NotificationsQueryVariables>;
|
||||
export const ConnectSignInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ConnectSignIn"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ConnectSignInInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignIn"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<ConnectSignInMutation, ConnectSignInMutationVariables>;
|
||||
export const SignOutDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SignOut"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignOut"}}]}}]} as unknown as DocumentNode<SignOutMutation, SignOutMutationVariables>;
|
||||
export const serverStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"serverState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PartialCloud"}}]}},{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"os"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hostname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updateExpiration"}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialCloud"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"minigraphql"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode<serverStateQuery, serverStateQueryVariables>;
|
||||
|
||||
112
web/helpers/create-apollo-client.ts
Normal file
112
web/helpers/create-apollo-client.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
import type {
|
||||
split as SplitType,
|
||||
ApolloClient as ApolloClientType,
|
||||
InMemoryCache as InMemoryCacheType,
|
||||
} from "@apollo/client";
|
||||
|
||||
import {
|
||||
from,
|
||||
ApolloClient,
|
||||
createHttpLink,
|
||||
InMemoryCache,
|
||||
split,
|
||||
// @ts-expect-error - CommonJS doesn't have type definitions
|
||||
} from "@apollo/client/core/core.cjs";
|
||||
|
||||
import { onError } from "@apollo/client/link/error";
|
||||
import { RetryLink } from "@apollo/client/link/retry";
|
||||
import { GraphQLWsLink } from "@apollo/client/link/subscriptions";
|
||||
import { getMainDefinition } from "@apollo/client/utilities";
|
||||
import { provideApolloClient } from "@vue/apollo-composable";
|
||||
import { createClient } from "graphql-ws";
|
||||
import { WEBGUI_GRAPHQL } from "./urls";
|
||||
|
||||
const httpEndpoint = WEBGUI_GRAPHQL;
|
||||
const wsEndpoint = new URL(WEBGUI_GRAPHQL.toString().replace("http", "ws"));
|
||||
|
||||
// const headers = { 'x-api-key': serverStore.apiKey };
|
||||
const headers = {};
|
||||
|
||||
const httpLink = createHttpLink({
|
||||
uri: httpEndpoint.toString(),
|
||||
headers,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const wsLink = new GraphQLWsLink(
|
||||
createClient({
|
||||
url: wsEndpoint.toString(),
|
||||
connectionParams: () => ({
|
||||
headers,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const errorLink = onError(({ graphQLErrors, networkError }: any) => {
|
||||
if (graphQLErrors) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
graphQLErrors.map((error: any) => {
|
||||
console.error("[GraphQL error]", error);
|
||||
const errorMsg =
|
||||
error.error && error.error.message
|
||||
? error.error.message
|
||||
: error.message;
|
||||
if (errorMsg && errorMsg.includes("offline")) {
|
||||
// @todo restart the api
|
||||
}
|
||||
return error.message;
|
||||
});
|
||||
}
|
||||
|
||||
if (networkError) {
|
||||
console.error(`[Network error]: ${networkError}`);
|
||||
const msg = networkError.message ? networkError.message : networkError;
|
||||
if (
|
||||
typeof msg === "string" &&
|
||||
msg.includes("Unexpected token < in JSON at position 0")
|
||||
) {
|
||||
return "Unraid API • CORS Error";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
});
|
||||
|
||||
const retryLink = new RetryLink({
|
||||
attempts: {
|
||||
max: 20,
|
||||
retryIf: (error, _operation) => {
|
||||
return Boolean(error);
|
||||
},
|
||||
},
|
||||
delay: {
|
||||
initial: 300,
|
||||
max: 10000,
|
||||
jitter: true,
|
||||
},
|
||||
});
|
||||
|
||||
const splitLinks = (split as typeof SplitType)(
|
||||
({ query }) => {
|
||||
const definition = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === "OperationDefinition" &&
|
||||
definition.operation === "subscription"
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
);
|
||||
/**
|
||||
* @todo as we add retries, determine which we'll need
|
||||
* https://www.apollographql.com/docs/react/api/link/introduction/#additive-composition
|
||||
* https://www.apollographql.com/docs/react/api/link/introduction/#directional-composition
|
||||
*/
|
||||
const additiveLink = from([errorLink, retryLink, splitLinks]);
|
||||
|
||||
const client: ApolloClientType<InMemoryCacheType> = new ApolloClient({
|
||||
link: additiveLink,
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
|
||||
provideApolloClient(client);
|
||||
@@ -63,14 +63,6 @@ export const useServerStore = defineStore('server', () => {
|
||||
* State
|
||||
*/
|
||||
const apiKey = ref<string>('');
|
||||
watch(apiKey, (newVal, oldVal) => {
|
||||
if (newVal) {
|
||||
return unraidApiStore.createApolloClient();
|
||||
}
|
||||
if (oldVal) {
|
||||
return unraidApiStore.closeUnraidApiClient();
|
||||
}
|
||||
});
|
||||
const apiVersion = ref<string>('');
|
||||
const array = ref<ServerStateArray | undefined>();
|
||||
// helps to display warning next to array status
|
||||
@@ -958,8 +950,9 @@ export const useServerStore = defineStore('server', () => {
|
||||
regGen: data.vars && data.vars.regGen ? parseInt(data.vars.regGen) : undefined,
|
||||
state: data.vars && data.vars.regState ? data.vars.regState : undefined,
|
||||
config: data.config
|
||||
? data.config
|
||||
? { id: 'config', ...data.config }
|
||||
: {
|
||||
id: 'config',
|
||||
error: data.vars && data.vars.configError ? data.vars.configError : undefined,
|
||||
valid: data.vars && data.vars.configValid ? data.vars.configValid : true,
|
||||
},
|
||||
|
||||
@@ -64,6 +64,7 @@ export const useThemeStore = defineStore('theme', () => {
|
||||
body.style.setProperty('--shadow-beta', `0 25px 50px -12px ${hexToRgba(beta, 0.15)}`);
|
||||
body.style.setProperty('--ring-offset-shadow', `0 0 ${beta}`);
|
||||
body.style.setProperty('--ring-shadow', `0 0 ${beta}`);
|
||||
body.style.setProperty('--dev-test', `0 0 ${beta}`);
|
||||
};
|
||||
|
||||
watch(theme, () => {
|
||||
|
||||
@@ -1,33 +1,17 @@
|
||||
import {
|
||||
from,
|
||||
ApolloClient,
|
||||
createHttpLink,
|
||||
InMemoryCache,
|
||||
split,
|
||||
// @ts-expect-error - CommonJS doesn't have type definitions
|
||||
} from "@apollo/client/core/core.cjs";
|
||||
import {
|
||||
type ApolloClient as ApolloClientType,
|
||||
type InMemoryCache as InMemoryCacheType,
|
||||
} from '@apollo/client';
|
||||
import { onError } from '@apollo/client/link/error';
|
||||
import { RetryLink } from '@apollo/client/link/retry';
|
||||
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
|
||||
import { getMainDefinition } from '@apollo/client/utilities';
|
||||
import { ArrowPathIcon, CogIcon } from '@heroicons/vue/24/solid';
|
||||
import { provideApolloClient } from '@vue/apollo-composable';
|
||||
} from "@apollo/client";
|
||||
import { ArrowPathIcon } from "@heroicons/vue/24/solid";
|
||||
// import { logErrorMessages } from '@vue/apollo-util';
|
||||
import { createClient } from 'graphql-ws';
|
||||
import { defineStore, createPinia, setActivePinia } from 'pinia';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
import { defineStore, createPinia, setActivePinia } from "pinia";
|
||||
import type { UserProfileLink } from "~/types/userProfile";
|
||||
|
||||
import { WebguiUnraidApiCommand } from '~/composables/services/webgui';
|
||||
import {
|
||||
WEBGUI_GRAPHQL,
|
||||
WEBGUI_SETTINGS_MANAGMENT_ACCESS,
|
||||
} from '~/helpers/urls';
|
||||
import { useErrorsStore } from '~/store/errors';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { WebguiUnraidApiCommand } from "~/composables/services/webgui";
|
||||
import { useErrorsStore } from "~/store/errors";
|
||||
import { useServerStore } from "~/store/server";
|
||||
|
||||
import "~/helpers/create-apollo-client";
|
||||
|
||||
/**
|
||||
* @see https://stackoverflow.com/questions/73476371/using-pinia-with-vue-js-web-components
|
||||
@@ -35,13 +19,7 @@ import { useServerStore } from '~/store/server';
|
||||
*/
|
||||
setActivePinia(createPinia());
|
||||
|
||||
const ERROR_CORS_403 =
|
||||
'The CORS policy for this site does not allow access from the specified Origin';
|
||||
|
||||
const httpEndpoint = WEBGUI_GRAPHQL;
|
||||
const wsEndpoint = new URL(WEBGUI_GRAPHQL.toString().replace('http', 'ws'));
|
||||
|
||||
export const useUnraidApiStore = defineStore('unraidApi', () => {
|
||||
export const useUnraidApiStore = defineStore("unraidApi", () => {
|
||||
const errorsStore = useErrorsStore();
|
||||
const serverStore = useServerStore();
|
||||
const unraidApiClient = ref<ApolloClientType<InMemoryCacheType> | null>(null);
|
||||
@@ -50,21 +28,21 @@ export const useUnraidApiStore = defineStore('unraidApi', () => {
|
||||
const apiResponse = serverStore.fetchServerFromApi();
|
||||
if (apiResponse) {
|
||||
// we have a response, so we're online
|
||||
unraidApiStatus.value = 'online';
|
||||
unraidApiStatus.value = "online";
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// const unraidApiErrors = ref<any[]>([]);
|
||||
const unraidApiStatus = ref<
|
||||
'connecting' | 'offline' | 'online' | 'restarting'
|
||||
>('offline');
|
||||
"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 unraidApiRestartAction = computed((): UserProfileLink | undefined => {
|
||||
const { connectPluginInstalled, stateDataError } = serverStore;
|
||||
if (
|
||||
unraidApiStatus.value !== 'offline' ||
|
||||
unraidApiStatus.value !== "offline" ||
|
||||
!connectPluginInstalled ||
|
||||
stateDataError
|
||||
) {
|
||||
@@ -74,132 +52,10 @@ export const useUnraidApiStore = defineStore('unraidApi', () => {
|
||||
click: () => restartUnraidApiClient(),
|
||||
emphasize: true,
|
||||
icon: ArrowPathIcon,
|
||||
text: 'Restart unraid-api',
|
||||
text: "Restart unraid-api",
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Automatically called when an apiKey is set in the serverStore
|
||||
*/
|
||||
const createApolloClient = () => {
|
||||
// return; // @todo remove
|
||||
unraidApiStatus.value = 'connecting';
|
||||
|
||||
// const headers = { 'x-api-key': serverStore.apiKey };
|
||||
const headers = {};
|
||||
|
||||
const httpLink = createHttpLink({
|
||||
uri: httpEndpoint.toString(),
|
||||
headers,
|
||||
credentials: "include",
|
||||
});
|
||||
|
||||
const wsLink = new GraphQLWsLink(
|
||||
createClient({
|
||||
url: wsEndpoint.toString(),
|
||||
connectionParams: () => ({
|
||||
headers,
|
||||
}),
|
||||
})
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const errorLink = onError(({ graphQLErrors, networkError }: any) => {
|
||||
if (graphQLErrors) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
graphQLErrors.map((error: any) => {
|
||||
console.error('[GraphQL error]', error);
|
||||
const errorMsg =
|
||||
error.error && error.error.message
|
||||
? error.error.message
|
||||
: error.message;
|
||||
if (errorMsg && errorMsg.includes('offline')) {
|
||||
unraidApiStatus.value = 'offline';
|
||||
// attempt to automatically restart the unraid-api
|
||||
if (unraidApiRestartAction) {
|
||||
restartUnraidApiClient();
|
||||
}
|
||||
}
|
||||
if (errorMsg && errorMsg.includes(ERROR_CORS_403)) {
|
||||
prioritizeCorsError.value = true;
|
||||
const msg = `<p>The CORS policy for the unraid-api does not allow access from the specified origin.</p><p>If you are using a reverse proxy, you need to copy your origin <strong class="font-mono"><em>${window.location.origin}</em></strong> and paste it into the "Extra Origins" list in the Connect settings.</p>`;
|
||||
errorsStore.setError({
|
||||
heading: 'Unraid API • CORS Error',
|
||||
message: msg,
|
||||
level: 'error',
|
||||
ref: 'unraidApiCorsError',
|
||||
type: 'unraidApiGQL',
|
||||
actions: [
|
||||
{
|
||||
href: `${WEBGUI_SETTINGS_MANAGMENT_ACCESS.toString()}#extraOriginsSettings`,
|
||||
icon: CogIcon,
|
||||
text: 'Go to Connect Settings',
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
return error.message;
|
||||
});
|
||||
}
|
||||
|
||||
if (networkError && !prioritizeCorsError) {
|
||||
console.error(`[Network error]: ${networkError}`);
|
||||
const msg = networkError.message ? networkError.message : networkError;
|
||||
if (
|
||||
typeof msg === 'string' &&
|
||||
msg.includes('Unexpected token < in JSON at position 0')
|
||||
) {
|
||||
return 'Unraid API • CORS Error';
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
});
|
||||
|
||||
const retryLink = new RetryLink({
|
||||
attempts: {
|
||||
max: 20,
|
||||
retryIf: (error, _operation) => {
|
||||
return !!error && !prioritizeCorsError; // don't retry when ERROR_CORS_403
|
||||
},
|
||||
},
|
||||
delay: {
|
||||
initial: prioritizeCorsError ? 3000 : 300,
|
||||
max: 10000,
|
||||
jitter: true,
|
||||
},
|
||||
});
|
||||
|
||||
interface Definintion {
|
||||
kind: string;
|
||||
operation?: string;
|
||||
}
|
||||
const splitLinks = split(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
({ query }: any) => {
|
||||
const definition: Definintion = getMainDefinition(query);
|
||||
return (
|
||||
definition.kind === 'OperationDefinition' &&
|
||||
definition.operation === 'subscription'
|
||||
);
|
||||
},
|
||||
wsLink,
|
||||
httpLink
|
||||
);
|
||||
/**
|
||||
* @todo as we add retries, determine which we'll need
|
||||
* https://www.apollographql.com/docs/react/api/link/introduction/#additive-composition
|
||||
* https://www.apollographql.com/docs/react/api/link/introduction/#directional-composition
|
||||
*/
|
||||
const additiveLink = from([errorLink, retryLink, splitLinks]);
|
||||
|
||||
const client: ApolloClientType<InMemoryCacheType> = new ApolloClient({
|
||||
link: additiveLink,
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
unraidApiClient.value = client;
|
||||
|
||||
provideApolloClient(client);
|
||||
};
|
||||
/**
|
||||
* Automatically called when an apiKey is unset in the serverStore
|
||||
*/
|
||||
@@ -219,31 +75,26 @@ export const useUnraidApiStore = defineStore('unraidApi', () => {
|
||||
* 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,
|
||||
});
|
||||
return setTimeout(() => {
|
||||
if (unraidApiClient.value) {
|
||||
createApolloClient();
|
||||
}
|
||||
}, 5000);
|
||||
} 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",
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -253,7 +104,6 @@ export const useUnraidApiStore = defineStore('unraidApi', () => {
|
||||
unraidApiStatus,
|
||||
prioritizeCorsError,
|
||||
unraidApiRestartAction,
|
||||
createApolloClient,
|
||||
closeUnraidApiClient,
|
||||
restartUnraidApiClient,
|
||||
};
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
export interface NotificationItemProps {
|
||||
id: string;
|
||||
title: string;
|
||||
subject: string;
|
||||
description: string;
|
||||
importance: string;
|
||||
link: string;
|
||||
type: 'success' | 'warning' | 'alert';
|
||||
timestamp: string;
|
||||
}
|
||||
Reference in New Issue
Block a user