From 3a843b6e16a278dad5b578c51656d597dfa0cbb7 Mon Sep 17 00:00:00 2001 From: Eli Bosley Date: Tue, 20 Aug 2024 14:44:40 -0400 Subject: [PATCH] feat: disable all legacy dashboard and network logic --- api/dev/Unraid.net/myservers.cfg | 2 +- api/dev/states/myservers.cfg | 6 +- api/docker-compose.yml | 1 + api/package.json | 2 +- .../common/dashboard/generate-server-data.ts | 154 ++++++++ api/src/graphql/generated/api/operations.ts | 189 ++++++++- api/src/graphql/generated/api/types.ts | 359 +++++++++++++++++- api/src/graphql/generated/client/gql.ts | 62 +++ api/src/graphql/generated/client/graphql.ts | 27 -- api/src/graphql/mothership/mutations.ts | 14 + .../resolvers/subscription/dashboard.ts | 91 ----- .../graphql/resolvers/subscription/network.ts | 75 +--- api/src/graphql/schema/types/base.graphql | 2 +- .../schema/types/connect/connect.graphql | 2 + .../schema/types/dashboard/dashboard.graphql | 93 +++++ .../schema/types/dashboard/network.graphql | 33 ++ .../schema/types/dashboard/service.graphql | 22 ++ .../types/notifications/notifications.graphql | 4 - api/src/mothership/subscribe-to-mothership.ts | 22 -- api/src/store/index.ts | 3 - api/src/unraid-api/graph/graph.module.ts | 2 + .../graph/resolvers/cloud/cloud.resolver.ts | 1 + .../dashboard/dashboard.resolver.spec.ts | 18 + .../resolvers/dashboard/dashboard.resolver.ts | 38 ++ .../notifications/notifications.resolver.ts | 44 --- .../graph/resolvers/resolvers.module.ts | 2 + 26 files changed, 990 insertions(+), 278 deletions(-) create mode 100644 api/src/common/dashboard/generate-server-data.ts create mode 100644 api/src/graphql/generated/client/gql.ts create mode 100644 api/src/graphql/mothership/mutations.ts delete mode 100644 api/src/graphql/resolvers/subscription/dashboard.ts create mode 100644 api/src/graphql/schema/types/dashboard/dashboard.graphql create mode 100644 api/src/graphql/schema/types/dashboard/network.graphql create mode 100644 api/src/graphql/schema/types/dashboard/service.graphql create mode 100644 api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.spec.ts create mode 100644 api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.ts diff --git a/api/dev/Unraid.net/myservers.cfg b/api/dev/Unraid.net/myservers.cfg index 9b28e9b25..4092fb321 100644 --- a/api/dev/Unraid.net/myservers.cfg +++ b/api/dev/Unraid.net/myservers.cfg @@ -1,5 +1,5 @@ [api] -version="3.8.1+fc0d7f4a" +version="3.8.1+7405443f" extraOrigins="https://google.com,https://test.com" [local] [notifier] diff --git a/api/dev/states/myservers.cfg b/api/dev/states/myservers.cfg index 975082217..13ca7a7ef 100644 --- a/api/dev/states/myservers.cfg +++ b/api/dev/states/myservers.cfg @@ -1,5 +1,5 @@ [api] -version="3.8.1+fc0d7f4a" +version="3.8.1+7405443f" extraOrigins="https://google.com,https://test.com" [local] [notifier] @@ -16,9 +16,9 @@ regWizTime="1611175408732_0951-1653-3509-FBA155FA23C0" idtoken="" accesstoken="" refreshtoken="" -allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com" +allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com" dynamicRemoteAccessType="DISABLED" [upc] apikey="unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810" [connectionStatus] -minigraph="PRE_INIT" +minigraph="CONNECTED" diff --git a/api/docker-compose.yml b/api/docker-compose.yml index 67297fa77..898dda4cf 100644 --- a/api/docker-compose.yml +++ b/api/docker-compose.yml @@ -27,6 +27,7 @@ x-volumes: &volumes networks: mothership_default: + external: true services: dev: diff --git a/api/package.json b/api/package.json index 88655c8a5..437ad2e86 100644 --- a/api/package.json +++ b/api/package.json @@ -29,7 +29,7 @@ "build:docker": "./scripts/dc.sh run --rm builder", "build-pkg": "./scripts/build.mjs", "codegen": "MOTHERSHIP_GRAPHQL_LINK='https://staging.mothership.unraid.net/ws' graphql-codegen --config codegen.yml -r dotenv/config './.env.staging'", - "codegen:watch": "DOTENV_CONFIG_PATH='./.env.staging' graphql-codegen-esm --config codegen.yml --watch -r dotenv/config", + "codegen:watch": "DOTENV_CONFIG_PATH='./.env.staging' graphql-codegen --config codegen.yml --watch -r dotenv/config", "codegen:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOTHERSHIP_GRAPHQL_LINK='https://mothership.localhost/ws' graphql-codegen-esm --config codegen.yml --watch", "tsc": "tsc --noEmit", "lint": "DEBUG=eslint:cli-engine eslint . --config .eslintrc.cjs", diff --git a/api/src/common/dashboard/generate-server-data.ts b/api/src/common/dashboard/generate-server-data.ts new file mode 100644 index 000000000..502008b94 --- /dev/null +++ b/api/src/common/dashboard/generate-server-data.ts @@ -0,0 +1,154 @@ +import { ConnectListAllDomainsFlags } from '@vmngr/libvirt'; +import { getHypervisor } from '@app/core/utils/vms/get-hypervisor'; +import { getUnraidVersion } from '@app/common/dashboard/get-unraid-version'; +import { bootTimestamp } from '@app/common/dashboard/boot-timestamp'; +import { getters, store } from '@app/store'; + +import { API_VERSION } from '@app/environment'; +import { DynamicRemoteAccessType } from '@app/remoteAccess/types'; +import { + DashboardService, + Dashboard, + DashboardArray, + ArrayState, +} from '@app/graphql/generated/api/types'; +import convert from 'convert'; +import { getArrayData } from '@app/core/modules/array/get-array-data'; +import { hostname } from 'os'; + +const getVmSummary = async (): Promise => { + try { + const hypervisor = await getHypervisor(); + if (!hypervisor) { + return { + installed: 0, + started: 0, + }; + } + + const activeDomains = (await hypervisor.connectListAllDomains( + ConnectListAllDomainsFlags.ACTIVE + )) as unknown[]; + const inactiveDomains = (await hypervisor.connectListAllDomains( + ConnectListAllDomainsFlags.INACTIVE + )) as unknown[]; + return { + installed: activeDomains.length + inactiveDomains.length, + started: activeDomains.length, + }; + } catch { + return { + installed: 0, + started: 0, + }; + } +}; + +const getDynamicRemoteAccessService = (): DashboardService | null => { + const { config, dynamicRemoteAccess } = store.getState(); + const enabledStatus = config.remote.dynamicRemoteAccessType; + + return { + name: 'dynamic-remote-access', + online: enabledStatus !== DynamicRemoteAccessType.DISABLED, + version: dynamicRemoteAccess.runningType, + uptime: { + timestamp: bootTimestamp.toISOString(), + }, + }; +}; + +const services = (): Dashboard['services'] => { + const dynamicRemoteAccess = getDynamicRemoteAccessService(); + return [ + { + name: 'unraid-api', + online: true, + uptime: { + timestamp: bootTimestamp.toISOString(), + }, + version: API_VERSION, + }, + ...(dynamicRemoteAccess ? [dynamicRemoteAccess] : []), + ]; +}; + +const KBToB = (kb: number | string): number => + convert(Number(kb), 'KB').to('B'); + +export const getArray = (): DashboardArray => { + const array = getArrayData(); + if (!array) { + return { + state: ArrayState.STOPPED, + capacity: { + bytes: { free: 0, used: 0, total: 0 }, + disks: { free: '0', used: '0', total: '0' }, + kilobytes: { free: '0', used: '0', total: '0' }, + }, + }; + } + + return { + state: array.state ?? ArrayState.STOPPED, + capacity: array.capacity, + }; +}; + +const getData = async (): Promise => { + const emhttp = getters.emhttp(); + const docker = getters.docker(); + + return { + id: hostname() ?? 'unraid', + vars: { + regState: emhttp.var.regState, + regTy: emhttp.var.regTy, + flashGuid: emhttp.var.flashGuid, + serverName: emhttp.var.name, + serverDescription: emhttp.var.comment, + }, + apps: { + installed: docker.installed ?? 0, + started: docker.running ?? 0, + }, + versions: { + unraid: await getUnraidVersion(), + }, + os: { + hostname: emhttp.var.name, + uptime: bootTimestamp.toISOString(), + }, + vms: await getVmSummary(), + array: getArray(), + services: services(), + display: { + case: { + url: '', + icon: '', + error: '', + base64: '', + }, + }, + config: emhttp.var.configValid + ? { valid: true } + : { + valid: false, + error: + { + error: 'UNKNOWN_ERROR', + invalid: 'INVALID', + nokeyserver: 'NO_KEY_SERVER', + withdrawn: 'WITHDRAWN', + }[emhttp.var.configState] ?? 'UNKNOWN_ERROR', + }, + }; +}; + +/** + * Provides a way to get dashboard data from the GraphQL client without the need for publishing to mothership + * @returns Dashboard data + */ +export const dashboardDataServer = async (): Promise => { + return await getData(); +}; diff --git a/api/src/graphql/generated/api/operations.ts b/api/src/graphql/generated/api/operations.ts index 8cf5d1174..f0bbe38d4 100755 --- a/api/src/graphql/generated/api/operations.ts +++ b/api/src/graphql/generated/api/operations.ts @@ -2,7 +2,7 @@ import * as Types from '@app/graphql/generated/api/types'; import { z } from 'zod' -import { AllowedOriginInput, ApiKey, ApiKeyResponse, ArrayType, ArrayCapacity, ArrayDisk, ArrayDiskFsColor, ArrayDiskStatus, ArrayDiskType, ArrayPendingState, ArrayState, Baseboard, Capacity, Case, Cloud, CloudResponse, Config, ConfigErrorState, ConnectSignInInput, ConnectUserInfoInput, ContainerHostConfig, ContainerMount, ContainerPort, ContainerPortType, ContainerState, Devices, Disk, DiskFsType, DiskInterfaceType, DiskPartition, DiskSmartStatus, Display, DockerContainer, DockerNetwork, Flash, Gpu, Importance, Info, InfoApps, InfoCpu, InfoMemory, KeyFile, Me, MemoryFormFactor, MemoryLayout, MemoryType, MinigraphStatus, MinigraphqlResponse, Mount, Network, Notification, NotificationFilter, NotificationInput, NotificationType, Os, Owner, ParityCheck, Partition, Pci, ProfileModel, Registration, RegistrationState, RelayResponse, RemoteAccess, Server, ServerStatus, Service, SetupRemoteAccessInput, Share, System, Temperature, Theme, UnassignedDevice, Uptime, Usb, User, UserAccount, Vars, Versions, VmDomain, VmState, Vms, WAN_ACCESS_TYPE, WAN_FORWARD_TYPE, Welcome, addApiKeyInput, addUserInput, arrayDiskInput, authenticateInput, deleteUserInput, mdState, registrationType, updateApikeyInput, usersInput } from '@app/graphql/generated/api/types' +import { AccessUrl, AccessUrlInput, AllowedOriginInput, ApiKey, ApiKeyResponse, ArrayType, ArrayCapacity, ArrayCapacityBytes, ArrayDisk, ArrayDiskFsColor, ArrayDiskStatus, ArrayDiskType, ArrayPendingState, ArrayState, Baseboard, Capacity, Case, Cloud, CloudResponse, Config, ConfigErrorState, ConnectSignInInput, ConnectUserInfoInput, ContainerHostConfig, ContainerMount, ContainerPort, ContainerPortType, ContainerState, Dashboard, DashboardApps, DashboardArray, DashboardCase, DashboardConfig, DashboardDisplay, DashboardOs, DashboardService, DashboardServiceInput, DashboardServiceUptime, DashboardServiceUptimeInput, DashboardTwoFactor, DashboardTwoFactorLocal, DashboardTwoFactorRemote, DashboardVars, DashboardVersions, DashboardVms, Devices, Disk, DiskFsType, DiskInterfaceType, DiskPartition, DiskSmartStatus, Display, DockerContainer, DockerNetwork, Flash, Gpu, Importance, Info, InfoApps, InfoCpu, InfoMemory, KeyFile, Me, MemoryFormFactor, MemoryLayout, MemoryType, MinigraphStatus, MinigraphqlResponse, Mount, Network, NetworkInput, Notification, NotificationFilter, NotificationInput, NotificationType, Os, Owner, ParityCheck, Partition, Pci, ProfileModel, Registration, RegistrationState, RelayResponse, RemoteAccess, Server, ServerStatus, Service, SetupRemoteAccessInput, Share, System, Temperature, Theme, URL_TYPE, UnassignedDevice, Uptime, Usb, User, UserAccount, Vars, Versions, VmDomain, VmState, Vms, WAN_ACCESS_TYPE, WAN_FORWARD_TYPE, Welcome, addApiKeyInput, addUserInput, arrayDiskInput, authenticateInput, deleteUserInput, mdState, registrationType, updateApikeyInput, usersInput } from '@app/graphql/generated/api/types' import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; type Properties = Required<{ @@ -55,6 +55,8 @@ export const TemperatureSchema = z.nativeEnum(Temperature); export const ThemeSchema = z.nativeEnum(Theme); +export const URL_TYPESchema = z.nativeEnum(URL_TYPE); + export const VmStateSchema = z.nativeEnum(VmState); export const WAN_ACCESS_TYPESchema = z.nativeEnum(WAN_ACCESS_TYPE); @@ -65,6 +67,25 @@ export const mdStateSchema = z.nativeEnum(mdState); export const registrationTypeSchema = z.nativeEnum(registrationType); +export function AccessUrlSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('AccessUrl').optional(), + ipv4: definedNonNullAnySchema.nullish(), + ipv6: definedNonNullAnySchema.nullish(), + name: z.string().nullish(), + type: URL_TYPESchema + }) +} + +export function AccessUrlInputSchema(): z.ZodObject> { + return z.object({ + ipv4: definedNonNullAnySchema.nullish(), + ipv6: definedNonNullAnySchema.nullish(), + name: z.string().nullish(), + type: URL_TYPESchema + }) +} + export function AllowedOriginInputSchema(): z.ZodObject> { return z.object({ origins: z.array(z.string()) @@ -107,11 +128,21 @@ export function ArrayTypeSchema(): z.ZodObject> { export function ArrayCapacitySchema(): z.ZodObject> { return z.object({ __typename: z.literal('ArrayCapacity').optional(), + bytes: ArrayCapacityBytesSchema().nullish(), disks: CapacitySchema(), kilobytes: CapacitySchema() }) } +export function ArrayCapacityBytesSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('ArrayCapacityBytes').optional(), + free: z.number().nullish(), + total: z.number().nullish(), + used: z.number().nullish() + }) +} + export function ArrayDiskSchema(): z.ZodObject> { return z.object({ __typename: z.literal('ArrayDisk').optional(), @@ -248,6 +279,155 @@ export function ContainerPortSchema(): z.ZodObject> { }) } +export function DashboardSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('Dashboard').optional(), + apps: DashboardAppsSchema().nullish(), + array: DashboardArraySchema().nullish(), + config: DashboardConfigSchema().nullish(), + display: DashboardDisplaySchema().nullish(), + id: z.string(), + lastPublish: z.string().nullish(), + network: NetworkSchema().nullish(), + online: z.boolean().nullish(), + os: DashboardOsSchema().nullish(), + services: z.array(DashboardServiceSchema().nullable()).nullish(), + twoFactor: DashboardTwoFactorSchema().nullish(), + vars: DashboardVarsSchema().nullish(), + versions: DashboardVersionsSchema().nullish(), + vms: DashboardVmsSchema().nullish() + }) +} + +export function DashboardAppsSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardApps').optional(), + installed: z.number().nullish(), + started: z.number().nullish() + }) +} + +export function DashboardArraySchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardArray').optional(), + capacity: ArrayCapacitySchema().nullish(), + state: z.string().nullish() + }) +} + +export function DashboardCaseSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardCase').optional(), + base64: z.string().nullish(), + error: z.string().nullish(), + icon: z.string().nullish(), + url: z.string().nullish() + }) +} + +export function DashboardConfigSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardConfig').optional(), + error: z.string().nullish(), + valid: z.boolean().nullish() + }) +} + +export function DashboardDisplaySchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardDisplay').optional(), + case: DashboardCaseSchema().nullish() + }) +} + +export function DashboardOsSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardOs').optional(), + hostname: z.string().nullish(), + uptime: z.string().nullish() + }) +} + +export function DashboardServiceSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardService').optional(), + name: z.string().nullish(), + online: z.boolean().nullish(), + uptime: DashboardServiceUptimeSchema().nullish(), + version: z.string().nullish() + }) +} + +export function DashboardServiceInputSchema(): z.ZodObject> { + return z.object({ + name: z.string(), + online: z.boolean(), + uptime: z.lazy(() => DashboardServiceUptimeInputSchema().nullish()), + version: z.string() + }) +} + +export function DashboardServiceUptimeSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardServiceUptime').optional(), + timestamp: z.string().nullish() + }) +} + +export function DashboardServiceUptimeInputSchema(): z.ZodObject> { + return z.object({ + timestamp: z.string() + }) +} + +export function DashboardTwoFactorSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardTwoFactor').optional(), + local: DashboardTwoFactorLocalSchema().nullish(), + remote: DashboardTwoFactorRemoteSchema().nullish() + }) +} + +export function DashboardTwoFactorLocalSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardTwoFactorLocal').optional(), + enabled: z.boolean().nullish() + }) +} + +export function DashboardTwoFactorRemoteSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardTwoFactorRemote').optional(), + enabled: z.boolean().nullish() + }) +} + +export function DashboardVarsSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardVars').optional(), + flashGuid: z.string().nullish(), + regState: z.string().nullish(), + regTy: z.string().nullish(), + serverDescription: z.string().nullish(), + serverName: z.string().nullish() + }) +} + +export function DashboardVersionsSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardVersions').optional(), + unraid: z.string().nullish() + }) +} + +export function DashboardVmsSchema(): z.ZodObject> { + return z.object({ + __typename: z.literal('DashboardVms').optional(), + installed: z.number().nullish(), + started: z.number().nullish() + }) +} + export function DevicesSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Devices').optional(), @@ -503,6 +683,7 @@ export function MountSchema(): z.ZodObject> { export function NetworkSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Network').optional(), + accessUrls: z.array(AccessUrlSchema()).nullish(), carrierChanges: z.string().nullish(), duplex: z.string().nullish(), iface: z.string().nullish(), @@ -518,6 +699,12 @@ export function NetworkSchema(): z.ZodObject> { }) } +export function NetworkInputSchema(): z.ZodObject> { + return z.object({ + accessUrls: z.array(z.lazy(() => AccessUrlInputSchema())) + }) +} + export function NotificationSchema(): z.ZodObject> { return z.object({ __typename: z.literal('Notification').optional(), diff --git a/api/src/graphql/generated/api/types.ts b/api/src/graphql/generated/api/types.ts index 154aebfc8..46cf5276f 100644 --- a/api/src/graphql/generated/api/types.ts +++ b/api/src/graphql/generated/api/types.ts @@ -20,9 +20,25 @@ export type Scalars = { JSON: { input: { [key: string]: any }; output: { [key: string]: any }; } Long: { input: number; output: number; } Port: { input: number; output: number; } + URL: { input: URL; output: URL; } UUID: { input: string; output: string; } }; +export type AccessUrl = { + __typename?: 'AccessUrl'; + ipv4?: Maybe; + ipv6?: Maybe; + name?: Maybe; + type: URL_TYPE; +}; + +export type AccessUrlInput = { + ipv4?: InputMaybe; + ipv6?: InputMaybe; + name?: InputMaybe; + type: URL_TYPE; +}; + export type AllowedOriginInput = { origins: Array; }; @@ -64,10 +80,18 @@ export type ArrayType = { export type ArrayCapacity = { __typename?: 'ArrayCapacity'; + bytes?: Maybe; disks: Capacity; kilobytes: Capacity; }; +export type ArrayCapacityBytes = { + __typename?: 'ArrayCapacityBytes'; + free?: Maybe; + total?: Maybe; + used?: Maybe; +}; + export type ArrayDisk = { __typename?: 'ArrayDisk'; /** User comment on disk */ @@ -294,6 +318,123 @@ export enum ContainerState { RUNNING = 'RUNNING' } +export type Dashboard = { + __typename?: 'Dashboard'; + apps?: Maybe; + array?: Maybe; + config?: Maybe; + display?: Maybe; + id: Scalars['ID']['output']; + lastPublish?: Maybe; + network?: Maybe; + online?: Maybe; + os?: Maybe; + services?: Maybe>>; + twoFactor?: Maybe; + vars?: Maybe; + versions?: Maybe; + vms?: Maybe; +}; + +export type DashboardApps = { + __typename?: 'DashboardApps'; + installed?: Maybe; + started?: Maybe; +}; + +export type DashboardArray = { + __typename?: 'DashboardArray'; + /** Current array capacity */ + capacity?: Maybe; + /** Current array state */ + state?: Maybe; +}; + +export type DashboardCase = { + __typename?: 'DashboardCase'; + base64?: Maybe; + error?: Maybe; + icon?: Maybe; + url?: Maybe; +}; + +export type DashboardConfig = { + __typename?: 'DashboardConfig'; + error?: Maybe; + valid?: Maybe; +}; + +export type DashboardDisplay = { + __typename?: 'DashboardDisplay'; + case?: Maybe; +}; + +export type DashboardOs = { + __typename?: 'DashboardOs'; + hostname?: Maybe; + uptime?: Maybe; +}; + +export type DashboardService = { + __typename?: 'DashboardService'; + name?: Maybe; + online?: Maybe; + uptime?: Maybe; + version?: Maybe; +}; + +export type DashboardServiceInput = { + name: Scalars['String']['input']; + online: Scalars['Boolean']['input']; + uptime?: InputMaybe; + version: Scalars['String']['input']; +}; + +export type DashboardServiceUptime = { + __typename?: 'DashboardServiceUptime'; + timestamp?: Maybe; +}; + +export type DashboardServiceUptimeInput = { + timestamp: Scalars['DateTime']['input']; +}; + +export type DashboardTwoFactor = { + __typename?: 'DashboardTwoFactor'; + local?: Maybe; + remote?: Maybe; +}; + +export type DashboardTwoFactorLocal = { + __typename?: 'DashboardTwoFactorLocal'; + enabled?: Maybe; +}; + +export type DashboardTwoFactorRemote = { + __typename?: 'DashboardTwoFactorRemote'; + enabled?: Maybe; +}; + +export type DashboardVars = { + __typename?: 'DashboardVars'; + flashGuid?: Maybe; + regState?: Maybe; + regTy?: Maybe; + serverDescription?: Maybe; + serverName?: Maybe; +}; + +export type DashboardVersions = { + __typename?: 'DashboardVersions'; + unraid?: Maybe; +}; + +export type DashboardVms = { + __typename?: 'DashboardVms'; + installed?: Maybe; + started?: Maybe; +}; + export type Devices = { __typename?: 'Devices'; gpu?: Maybe>>; @@ -588,7 +729,6 @@ export type Mutation = { removeDiskFromArray?: Maybe; /** Resume parity check */ resumeParityCheck?: Maybe; - sendNotification?: Maybe; setAdditionalAllowedOrigins: Array; setupRemoteAccess: Scalars['Boolean']['output']; shutdown?: Maybe; @@ -601,6 +741,7 @@ export type Mutation = { unmountArrayDisk?: Maybe; /** Update an existing API key */ updateApikey?: Maybe; + updateNetwork: Network; }; @@ -657,11 +798,6 @@ export type MutationremoveDiskFromArrayArgs = { }; -export type MutationsendNotificationArgs = { - notification: NotificationInput; -}; - - export type MutationsetAdditionalAllowedOriginsArgs = { input: AllowedOriginInput; }; @@ -687,8 +823,14 @@ export type MutationupdateApikeyArgs = { name: Scalars['String']['input']; }; + +export type MutationupdateNetworkArgs = { + data: NetworkInput; +}; + export type Network = { __typename?: 'Network'; + accessUrls?: Maybe>; carrierChanges?: Maybe; duplex?: Maybe; iface?: Maybe; @@ -703,6 +845,10 @@ export type Network = { type?: Maybe; }; +export type NetworkInput = { + accessUrls: Array; +}; + export type Notification = { __typename?: 'Notification'; description: Scalars['String']['output']; @@ -886,6 +1032,8 @@ export type Query = { registration?: Maybe; remoteAccess: RemoteAccess; server?: Maybe; + /** Temporary Type to Enable Swapping Dashboard over in Connect without major changes */ + serverDashboard: Dashboard; servers: Array; /** Network Shares */ shares?: Maybe>>; @@ -1133,6 +1281,14 @@ export enum Theme { WHITE = 'white' } +export enum URL_TYPE { + DEFAULT = 'DEFAULT', + LAN = 'LAN', + MDNS = 'MDNS', + WAN = 'WAN', + WIREGUARD = 'WIREGUARD' +} + export type UnassignedDevice = { __typename?: 'UnassignedDevice'; devlinks?: Maybe; @@ -1582,11 +1738,14 @@ export type ResolversInterfaceTypes> = R /** Mapping between all available schema types and the resolvers types */ export type ResolversTypes = ResolversObject<{ + AccessUrl: ResolverTypeWrapper; + AccessUrlInput: AccessUrlInput; AllowedOriginInput: AllowedOriginInput; ApiKey: ResolverTypeWrapper; ApiKeyResponse: ResolverTypeWrapper; Array: ResolverTypeWrapper; ArrayCapacity: ResolverTypeWrapper; + ArrayCapacityBytes: ResolverTypeWrapper; ArrayDisk: ResolverTypeWrapper; ArrayDiskFsColor: ArrayDiskFsColor; ArrayDiskStatus: ArrayDiskStatus; @@ -1608,6 +1767,23 @@ export type ResolversTypes = ResolversObject<{ ContainerPort: ResolverTypeWrapper; ContainerPortType: ContainerPortType; ContainerState: ContainerState; + Dashboard: ResolverTypeWrapper; + DashboardApps: ResolverTypeWrapper; + DashboardArray: ResolverTypeWrapper; + DashboardCase: ResolverTypeWrapper; + DashboardConfig: ResolverTypeWrapper; + DashboardDisplay: ResolverTypeWrapper; + DashboardOs: ResolverTypeWrapper; + DashboardService: ResolverTypeWrapper; + DashboardServiceInput: DashboardServiceInput; + DashboardServiceUptime: ResolverTypeWrapper; + DashboardServiceUptimeInput: DashboardServiceUptimeInput; + DashboardTwoFactor: ResolverTypeWrapper; + DashboardTwoFactorLocal: ResolverTypeWrapper; + DashboardTwoFactorRemote: ResolverTypeWrapper; + DashboardVars: ResolverTypeWrapper; + DashboardVersions: ResolverTypeWrapper; + DashboardVms: ResolverTypeWrapper; DateTime: ResolverTypeWrapper; Devices: ResolverTypeWrapper; Disk: ResolverTypeWrapper; @@ -1640,6 +1816,7 @@ export type ResolversTypes = ResolversObject<{ Mount: ResolverTypeWrapper; Mutation: ResolverTypeWrapper<{}>; Network: ResolverTypeWrapper; + NetworkInput: NetworkInput; Notification: ResolverTypeWrapper; NotificationFilter: NotificationFilter; NotificationInput: NotificationInput; @@ -1666,6 +1843,8 @@ export type ResolversTypes = ResolversObject<{ System: ResolverTypeWrapper; Temperature: Temperature; Theme: Theme; + URL: ResolverTypeWrapper; + URL_TYPE: URL_TYPE; UUID: ResolverTypeWrapper; UnassignedDevice: ResolverTypeWrapper; Uptime: ResolverTypeWrapper; @@ -1693,11 +1872,14 @@ export type ResolversTypes = ResolversObject<{ /** Mapping between all available schema types and the resolvers parents */ export type ResolversParentTypes = ResolversObject<{ + AccessUrl: AccessUrl; + AccessUrlInput: AccessUrlInput; AllowedOriginInput: AllowedOriginInput; ApiKey: ApiKey; ApiKeyResponse: ApiKeyResponse; Array: ArrayType; ArrayCapacity: ArrayCapacity; + ArrayCapacityBytes: ArrayCapacityBytes; ArrayDisk: ArrayDisk; Baseboard: Baseboard; Boolean: Scalars['Boolean']['output']; @@ -1711,6 +1893,23 @@ export type ResolversParentTypes = ResolversObject<{ ContainerHostConfig: ContainerHostConfig; ContainerMount: ContainerMount; ContainerPort: ContainerPort; + Dashboard: Dashboard; + DashboardApps: DashboardApps; + DashboardArray: DashboardArray; + DashboardCase: DashboardCase; + DashboardConfig: DashboardConfig; + DashboardDisplay: DashboardDisplay; + DashboardOs: DashboardOs; + DashboardService: DashboardService; + DashboardServiceInput: DashboardServiceInput; + DashboardServiceUptime: DashboardServiceUptime; + DashboardServiceUptimeInput: DashboardServiceUptimeInput; + DashboardTwoFactor: DashboardTwoFactor; + DashboardTwoFactorLocal: DashboardTwoFactorLocal; + DashboardTwoFactorRemote: DashboardTwoFactorRemote; + DashboardVars: DashboardVars; + DashboardVersions: DashboardVersions; + DashboardVms: DashboardVms; DateTime: Scalars['DateTime']['output']; Devices: Devices; Disk: Disk; @@ -1736,6 +1935,7 @@ export type ResolversParentTypes = ResolversObject<{ Mount: Mount; Mutation: {}; Network: Network; + NetworkInput: NetworkInput; Notification: Notification; NotificationFilter: NotificationFilter; NotificationInput: NotificationInput; @@ -1757,6 +1957,7 @@ export type ResolversParentTypes = ResolversObject<{ String: Scalars['String']['output']; Subscription: {}; System: System; + URL: Scalars['URL']['output']; UUID: Scalars['UUID']['output']; UnassignedDevice: UnassignedDevice; Uptime: Uptime; @@ -1777,6 +1978,14 @@ export type ResolversParentTypes = ResolversObject<{ usersInput: usersInput; }>; +export type AccessUrlResolvers = ResolversObject<{ + ipv4?: Resolver, ParentType, ContextType>; + ipv6?: Resolver, ParentType, ContextType>; + name?: Resolver, ParentType, ContextType>; + type?: Resolver; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type ApiKeyResolvers = ResolversObject<{ description?: Resolver, ParentType, ContextType>; expiresAt?: Resolver; @@ -1805,11 +2014,19 @@ export type ArrayResolvers; export type ArrayCapacityResolvers = ResolversObject<{ + bytes?: Resolver, ParentType, ContextType>; disks?: Resolver; kilobytes?: Resolver; __isTypeOf?: IsTypeOfResolverFn; }>; +export type ArrayCapacityBytesResolvers = ResolversObject<{ + free?: Resolver, ParentType, ContextType>; + total?: Resolver, ParentType, ContextType>; + used?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export type ArrayDiskResolvers = ResolversObject<{ comment?: Resolver, ParentType, ContextType>; critical?: Resolver, ParentType, ContextType>; @@ -1908,6 +2125,110 @@ export type ContainerPortResolvers; }>; +export type DashboardResolvers = ResolversObject<{ + apps?: Resolver, ParentType, ContextType>; + array?: Resolver, ParentType, ContextType>; + config?: Resolver, ParentType, ContextType>; + display?: Resolver, ParentType, ContextType>; + id?: Resolver; + lastPublish?: Resolver, ParentType, ContextType>; + network?: Resolver, ParentType, ContextType>; + online?: Resolver, ParentType, ContextType>; + os?: Resolver, ParentType, ContextType>; + services?: Resolver>>, ParentType, ContextType>; + twoFactor?: Resolver, ParentType, ContextType>; + vars?: Resolver, ParentType, ContextType>; + versions?: Resolver, ParentType, ContextType>; + vms?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardAppsResolvers = ResolversObject<{ + installed?: Resolver, ParentType, ContextType>; + started?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardArrayResolvers = ResolversObject<{ + capacity?: Resolver, ParentType, ContextType>; + state?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardCaseResolvers = ResolversObject<{ + base64?: Resolver, ParentType, ContextType>; + error?: Resolver, ParentType, ContextType>; + icon?: Resolver, ParentType, ContextType>; + url?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardConfigResolvers = ResolversObject<{ + error?: Resolver, ParentType, ContextType>; + valid?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardDisplayResolvers = ResolversObject<{ + case?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardOsResolvers = ResolversObject<{ + hostname?: Resolver, ParentType, ContextType>; + uptime?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardServiceResolvers = ResolversObject<{ + name?: Resolver, ParentType, ContextType>; + online?: Resolver, ParentType, ContextType>; + uptime?: Resolver, ParentType, ContextType>; + version?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardServiceUptimeResolvers = ResolversObject<{ + timestamp?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardTwoFactorResolvers = ResolversObject<{ + local?: Resolver, ParentType, ContextType>; + remote?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardTwoFactorLocalResolvers = ResolversObject<{ + enabled?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardTwoFactorRemoteResolvers = ResolversObject<{ + enabled?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardVarsResolvers = ResolversObject<{ + flashGuid?: Resolver, ParentType, ContextType>; + regState?: Resolver, ParentType, ContextType>; + regTy?: Resolver, ParentType, ContextType>; + serverDescription?: Resolver, ParentType, ContextType>; + serverName?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardVersionsResolvers = ResolversObject<{ + unraid?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + +export type DashboardVmsResolvers = ResolversObject<{ + installed?: Resolver, ParentType, ContextType>; + started?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}>; + export interface DateTimeScalarConfig extends GraphQLScalarTypeConfig { name: 'DateTime'; } @@ -2154,7 +2475,6 @@ export type MutationResolvers, ParentType, ContextType>; removeDiskFromArray?: Resolver, ParentType, ContextType, Partial>; resumeParityCheck?: Resolver, ParentType, ContextType>; - sendNotification?: Resolver, ParentType, ContextType, RequireFields>; setAdditionalAllowedOrigins?: Resolver, ParentType, ContextType, RequireFields>; setupRemoteAccess?: Resolver>; shutdown?: Resolver, ParentType, ContextType>; @@ -2163,9 +2483,11 @@ export type MutationResolvers, ParentType, ContextType>; unmountArrayDisk?: Resolver, ParentType, ContextType, RequireFields>; updateApikey?: Resolver, ParentType, ContextType, RequireFields>; + updateNetwork?: Resolver>; }>; export type NetworkResolvers = ResolversObject<{ + accessUrls?: Resolver>, ParentType, ContextType>; carrierChanges?: Resolver, ParentType, ContextType>; duplex?: Resolver, ParentType, ContextType>; iface?: Resolver, ParentType, ContextType>; @@ -2334,6 +2656,7 @@ export type QueryResolvers, ParentType, ContextType>; remoteAccess?: Resolver; server?: Resolver, ParentType, ContextType>; + serverDashboard?: Resolver; servers?: Resolver, ParentType, ContextType>; shares?: Resolver>>, ParentType, ContextType>; unassignedDevices?: Resolver>>, ParentType, ContextType>; @@ -2446,6 +2769,10 @@ export type SystemResolvers; }>; +export interface URLScalarConfig extends GraphQLScalarTypeConfig { + name: 'URL'; +} + export interface UUIDScalarConfig extends GraphQLScalarTypeConfig { name: 'UUID'; } @@ -2728,10 +3055,12 @@ export type WelcomeResolvers; export type Resolvers = ResolversObject<{ + AccessUrl?: AccessUrlResolvers; ApiKey?: ApiKeyResolvers; ApiKeyResponse?: ApiKeyResponseResolvers; Array?: ArrayResolvers; ArrayCapacity?: ArrayCapacityResolvers; + ArrayCapacityBytes?: ArrayCapacityBytesResolvers; ArrayDisk?: ArrayDiskResolvers; Baseboard?: BaseboardResolvers; Capacity?: CapacityResolvers; @@ -2742,6 +3071,21 @@ export type Resolvers = ResolversObject<{ ContainerHostConfig?: ContainerHostConfigResolvers; ContainerMount?: ContainerMountResolvers; ContainerPort?: ContainerPortResolvers; + Dashboard?: DashboardResolvers; + DashboardApps?: DashboardAppsResolvers; + DashboardArray?: DashboardArrayResolvers; + DashboardCase?: DashboardCaseResolvers; + DashboardConfig?: DashboardConfigResolvers; + DashboardDisplay?: DashboardDisplayResolvers; + DashboardOs?: DashboardOsResolvers; + DashboardService?: DashboardServiceResolvers; + DashboardServiceUptime?: DashboardServiceUptimeResolvers; + DashboardTwoFactor?: DashboardTwoFactorResolvers; + DashboardTwoFactorLocal?: DashboardTwoFactorLocalResolvers; + DashboardTwoFactorRemote?: DashboardTwoFactorRemoteResolvers; + DashboardVars?: DashboardVarsResolvers; + DashboardVersions?: DashboardVersionsResolvers; + DashboardVms?: DashboardVmsResolvers; DateTime?: GraphQLScalarType; Devices?: DevicesResolvers; Disk?: DiskResolvers; @@ -2781,6 +3125,7 @@ export type Resolvers = ResolversObject<{ Share?: ShareResolvers; Subscription?: SubscriptionResolvers; System?: SystemResolvers; + URL?: GraphQLScalarType; UUID?: GraphQLScalarType; UnassignedDevice?: UnassignedDeviceResolvers; Uptime?: UptimeResolvers; diff --git a/api/src/graphql/generated/client/gql.ts b/api/src/graphql/generated/client/gql.ts new file mode 100644 index 000000000..41a307852 --- /dev/null +++ b/api/src/graphql/generated/client/gql.ts @@ -0,0 +1,62 @@ +/* eslint-disable */ +import * as types from './graphql.js'; +import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core'; + +/** + * Map of all GraphQL operations in the project. + * + * This map has several performance disadvantages: + * 1. It is not tree-shakeable, so it will include all operations in the project. + * 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle. + * 3. It does not support dead code elimination, so it will add unused operations. + * + * Therefore it is highly recommended to use the babel or swc plugin for production. + */ +const documents = { + "\nmutation sendRemoteAccessMutation($remoteAccess: RemoteAccessInput!) {\n\tremoteSession(remoteAccess: $remoteAccess)\n}\n": types.sendRemoteAccessMutationDocument, + "\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n": types.sendRemoteGraphQLResponseDocument, + "\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n": types.RemoteGraphQLEventFragmentFragmentDoc, + "\n fragment RemoteAccessEventFragment on RemoteAccessEvent {\n type\n data {\n type\n url {\n type\n name\n ipv4\n ipv6\n }\n apiKey\n }\n }\n": types.RemoteAccessEventFragmentFragmentDoc, + "\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteAccessEventFragment\n ...RemoteGraphQLEventFragment\n }\n }\n": types.eventsDocument, +}; + +/** + * The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients. + * + * + * @example + * ```ts + * const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`); + * ``` + * + * The query argument is unknown! + * Please regenerate the types. + */ +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: "\nmutation sendRemoteAccessMutation($remoteAccess: RemoteAccessInput!) {\n\tremoteSession(remoteAccess: $remoteAccess)\n}\n"): (typeof documents)["\nmutation sendRemoteAccessMutation($remoteAccess: RemoteAccessInput!) {\n\tremoteSession(remoteAccess: $remoteAccess)\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 mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n"): (typeof documents)["\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\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 fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n"): (typeof documents)["\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\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 fragment RemoteAccessEventFragment on RemoteAccessEvent {\n type\n data {\n type\n url {\n type\n name\n ipv4\n ipv6\n }\n apiKey\n }\n }\n"): (typeof documents)["\n fragment RemoteAccessEventFragment on RemoteAccessEvent {\n type\n data {\n type\n url {\n type\n name\n ipv4\n ipv6\n }\n apiKey\n }\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 subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteAccessEventFragment\n ...RemoteGraphQLEventFragment\n }\n }\n"): (typeof documents)["\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteAccessEventFragment\n ...RemoteGraphQLEventFragment\n }\n }\n"]; + +export function graphql(source: string) { + return (documents as any)[source] ?? {}; +} + +export type DocumentType> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never; \ No newline at end of file diff --git a/api/src/graphql/generated/client/graphql.ts b/api/src/graphql/generated/client/graphql.ts index a17fed8c2..4b92620e4 100644 --- a/api/src/graphql/generated/client/graphql.ts +++ b/api/src/graphql/generated/client/graphql.ts @@ -709,30 +709,6 @@ export type Vars = { regTy?: Maybe; }; -export type updateDashboardMutationVariables = Exact<{ - data: DashboardInput; - apiKey: Scalars['String']['input']; -}>; - - -export type updateDashboardMutation = { __typename?: 'Mutation', updateDashboard: { __typename?: 'Dashboard', apps?: { __typename?: 'DashboardApps', installed?: number | null } | null } }; - -export type sendNotificationMutationVariables = Exact<{ - notification: NotificationInput; - apiKey: Scalars['String']['input']; -}>; - - -export type sendNotificationMutation = { __typename?: 'Mutation', sendNotification?: { __typename?: 'Notification', title?: string | null, subject?: string | null, description?: string | null, importance?: Importance | null, link?: string | null, status: NotificationStatus } | null }; - -export type updateNetworkMutationVariables = Exact<{ - data: NetworkInput; - apiKey: Scalars['String']['input']; -}>; - - -export type updateNetworkMutation = { __typename?: 'Mutation', updateNetwork: { __typename?: 'Network', accessUrls?: Array<{ __typename?: 'AccessUrl', name?: string | null, type: URL_TYPE, ipv4?: URL | null, ipv6?: URL | null }> | null } }; - export type sendRemoteAccessMutationMutationVariables = Exact<{ remoteAccess: RemoteAccessInput; }>; @@ -764,9 +740,6 @@ export type eventsSubscription = { __typename?: 'Subscription', events?: Array<{ export const RemoteGraphQLEventFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"remoteGraphQLEventData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"sha256"}}]}}]}}]} as unknown as DocumentNode; export const RemoteAccessEventFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteAccessEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteAccessEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"url"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"ipv4"}},{"kind":"Field","name":{"kind":"Name","value":"ipv6"}}]}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}}]}}]} as unknown as DocumentNode; -export const updateDashboardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateDashboard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DashboardInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateDashboard"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"directives":[{"kind":"Directive","name":{"kind":"Name","value":"auth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"apiKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}}}]}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"installed"}}]}}]}}]}}]} as unknown as DocumentNode; -export const sendNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"sendNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"notification"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"notification"},"value":{"kind":"Variable","name":{"kind":"Name","value":"notification"}}}],"directives":[{"kind":"Directive","name":{"kind":"Name","value":"auth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"apiKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}}}]}],"selectionSet":{"kind":"SelectionSet","selections":[{"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":"status"}}]}}]}}]} as unknown as DocumentNode; -export const updateNetworkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateNetwork"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NetworkInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateNetwork"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"directives":[{"kind":"Directive","name":{"kind":"Name","value":"auth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"apiKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}}}]}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"ipv4"}},{"kind":"Field","name":{"kind":"Name","value":"ipv6"}}]}}]}}]}}]} as unknown as DocumentNode; export const sendRemoteAccessMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"sendRemoteAccessMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"remoteAccess"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteAccessInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"remoteSession"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"remoteAccess"},"value":{"kind":"Variable","name":{"kind":"Name","value":"remoteAccess"}}}]}]}}]} as unknown as DocumentNode; export const sendRemoteGraphQLResponseDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"sendRemoteGraphQLResponse"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLServerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"remoteGraphQLResponse"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode; export const eventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientConnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"connectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"connectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientDisconnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"disconnectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"disconnectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteAccessEventFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteAccessEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteAccessEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"url"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"ipv4"}},{"kind":"Field","name":{"kind":"Name","value":"ipv6"}}]}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"remoteGraphQLEventData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"sha256"}}]}}]}}]} as unknown as DocumentNode; \ No newline at end of file diff --git a/api/src/graphql/mothership/mutations.ts b/api/src/graphql/mothership/mutations.ts new file mode 100644 index 000000000..59488fd9d --- /dev/null +++ b/api/src/graphql/mothership/mutations.ts @@ -0,0 +1,14 @@ +import { graphql } from '@app/graphql/generated/client/gql'; + +// This doesn't need auth directive, new mothership can handle clients with no auth directives +export const SEND_DYNAMIC_REMOTE_ACCESS_MUTATION = graphql(/* GraphQL */ ` +mutation sendRemoteAccessMutation($remoteAccess: RemoteAccessInput!) { + remoteSession(remoteAccess: $remoteAccess) +} +`); + +export const SEND_REMOTE_QUERY_RESPONSE = graphql(/* GraphQL */ ` + mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) { + remoteGraphQLResponse(input: $input) + } +`); diff --git a/api/src/graphql/resolvers/subscription/dashboard.ts b/api/src/graphql/resolvers/subscription/dashboard.ts deleted file mode 100644 index aae1772c2..000000000 --- a/api/src/graphql/resolvers/subscription/dashboard.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { dashboardLogger } from '@app/core/log'; -import { generateData } from '@app/common/dashboard/generate-data'; -import { PUBSUB_CHANNEL, pubsub } from '@app/core/pubsub'; -import { getters, store } from '@app/store'; -import { saveDataPacket } from '@app/store/modules/dashboard'; -import { isEqual } from 'lodash'; -import { GraphQLClient } from '@app/mothership/graphql-client'; -import { SEND_DASHBOARD_PAYLOAD_MUTATION } from '../../mothership/mutations'; -import { type DashboardInput } from '../../generated/client/graphql'; -import { getDiff } from 'json-difference'; -import { DEBUG } from '@app/environment'; -import { isApolloError } from '@apollo/client/core'; - -const isNumberBetween = (min: number, max: number) => (num: number) => num > min && num < max; - -const logAndReturn = (returnValue: T, logLevel: 'info' | 'debug' | 'trace' | 'error', logLine: string, ...logParams: unknown[]): T => { - dashboardLogger[logLevel](logLine, ...logParams); - return returnValue; -}; - -const ONE_MB = 1_024 * 1_024; -const ONE_HUNDRED_MB = 100 * ONE_MB; - -const canSendDataPacket = (dataPacket: DashboardInput | null) => { - const { lastDataPacketTimestamp, lastDataPacket } = getters.dashboard(); - // Const { lastDataPacketTimestamp, lastDataPacketString, lastDataPacket } = dashboardStore; - if (!dataPacket) return logAndReturn(false, 'error', 'Not sending update to dashboard becuase the data packet is empty'); - - // UPDATE - No data packet has been sent since boot - if (!lastDataPacketTimestamp) return logAndReturn(true, 'debug', 'Sending update as none have been sent since the API started'); - - // NO_UPDATE - This is an exact copy of the last data packet - if (isEqual(dataPacket, lastDataPacket)) return logAndReturn(false, 'trace', '[NETWORK] Skipping Update'); - - if (!lastDataPacket) return logAndReturn(true, 'debug', 'Sending update as no data packets have been stored in state yet'); - - const difference = getDiff(lastDataPacket, dataPacket); - - const oldBytesFree = lastDataPacket.array?.capacity.bytes?.free; - const newBytesFree = dataPacket.array?.capacity.bytes?.free; - - if (oldBytesFree && newBytesFree && difference.added.length === 0 && difference.removed.length === 0 && difference.edited.length === 2) { - // If size has changed less than 100 MB (and nothing else has changed), don't send an update - - const numberBetweenCheck = isNumberBetween((Number(oldBytesFree) * ONE_MB) - ONE_HUNDRED_MB, (Number(oldBytesFree) * ONE_MB) + ONE_HUNDRED_MB); - if (numberBetweenCheck(Number(newBytesFree) * ONE_MB)) { - logAndReturn(false, 'info', 'Size has not changed enough to send a new dashboard payload'); - } - } - - return logAndReturn(true, 'trace', 'Sending update because the packets are not equal'); -}; - -export const publishToDashboard = async () => { - try { - const dataPacket = await generateData(); - // Only update data on change - if (!canSendDataPacket(dataPacket)) return; - - dashboardLogger.debug('New Data Packet Is: %o', dataPacket); - - // Save new data packet - store.dispatch(saveDataPacket({ lastDataPacket: dataPacket })); - - // Publish the updated data - dashboardLogger.trace({ dataPacket } , 'Publishing update'); - - // Update local clients - await pubsub.publish(PUBSUB_CHANNEL.DASHBOARD, { - dashboard: dataPacket, - }); - if (dataPacket) { - const client = GraphQLClient.getInstance(); - if (!client) { - throw new Error('Invalid Client'); - } - - // Update mothership - await client.mutate({ mutation: SEND_DASHBOARD_PAYLOAD_MUTATION, variables: { apiKey: getters.config().remote.apikey, data: dataPacket } }); - } else { - dashboardLogger.error('DataPacket Was Empty'); - } - } catch (error: unknown) { - if (error instanceof Error && isApolloError(error)) { - dashboardLogger.error('Failed publishing with GQL Errors: %s, \nClient Errors: %s', error.graphQLErrors.map(error => error.message).join(','), error.clientErrors.join(', ')); - } - - if (DEBUG) dashboardLogger.error(error); - } -}; - diff --git a/api/src/graphql/resolvers/subscription/network.ts b/api/src/graphql/resolvers/subscription/network.ts index 660a87b8d..23b2bd2bf 100644 --- a/api/src/graphql/resolvers/subscription/network.ts +++ b/api/src/graphql/resolvers/subscription/network.ts @@ -1,21 +1,13 @@ -import { GraphQLClient } from '@app/mothership/graphql-client'; import { type Nginx } from '@app/core/types/states/nginx'; -import { type RootState, store, getters } from '@app/store'; +import { type RootState, store } from '@app/store'; import { - type NetworkInput, URL_TYPE, type AccessUrlInput, } from '@app/graphql/generated/client/graphql'; -import { dashboardLogger, logger } from '@app/core'; -import { isEqual } from 'lodash'; -import { SEND_NETWORK_MUTATION } from '@app/graphql/mothership/mutations'; -import { saveNetworkPacket } from '@app/store/modules/dashboard'; -import { ApolloError } from '@apollo/client/core/core.cjs'; +import { logger } from '@app/core'; import { AccessUrlInputSchema, - NetworkInputSchema, } from '@app/graphql/generated/client/validators'; -import { ZodError } from 'zod'; interface UrlForFieldInput { url: string; @@ -271,66 +263,3 @@ export const getServerIps = ( return { urls: safeUrls, errors }; }; - -export const publishNetwork = async () => { - try { - const client = GraphQLClient.getInstance(); - - const datapacket = getServerIps(); - if (datapacket.errors) { - const zodErrors = datapacket.errors.filter( - (error) => error instanceof ZodError - ); - if (zodErrors.length) { - dashboardLogger.warn( - 'Validation Errors Encountered with Network Payload: %s', - zodErrors.map((error) => error.message).join(',') - ); - } - } - const networkPacket: NetworkInput = { accessUrls: datapacket.urls }; - const validatedNetwork = NetworkInputSchema().parse(networkPacket); - - const { lastNetworkPacket } = getters.dashboard(); - const { apikey: apiKey } = getters.config().remote; - if ( - isEqual( - JSON.stringify(lastNetworkPacket), - JSON.stringify(validatedNetwork) - ) - ) { - dashboardLogger.trace('[DASHBOARD] Skipping Update'); - } else if (client) { - dashboardLogger.info( - { validatedNetwork }, - 'Sending data packet for network' - ); - const result = await client.mutate({ - mutation: SEND_NETWORK_MUTATION, - variables: { - apiKey, - data: validatedNetwork, - }, - }); - dashboardLogger.debug( - { result }, - 'Sent network mutation with %s urls', - datapacket.urls.length - ); - store.dispatch( - saveNetworkPacket({ lastNetworkPacket: validatedNetwork }) - ); - } - } catch (error: unknown) { - dashboardLogger.trace('ERROR', error); - if (error instanceof ApolloError) { - dashboardLogger.error( - 'Failed publishing with GQL Errors: %s, \nClient Errors: %s', - (error as ApolloError).graphQLErrors.map((error) => error.message).join(','), - (error as ApolloError).clientErrors.join(', ') - ); - } else { - dashboardLogger.error(error); - } - } -}; diff --git a/api/src/graphql/schema/types/base.graphql b/api/src/graphql/schema/types/base.graphql index 581da86f5..02f7fd654 100644 --- a/api/src/graphql/schema/types/base.graphql +++ b/api/src/graphql/schema/types/base.graphql @@ -3,6 +3,7 @@ scalar Long scalar UUID scalar DateTime scalar Port +scalar URL type Welcome { message: String! @@ -16,7 +17,6 @@ type Query { type Mutation { login(username: String!, password: String!): String - sendNotification(notification: NotificationInput!): Notification shutdown: String reboot: String } diff --git a/api/src/graphql/schema/types/connect/connect.graphql b/api/src/graphql/schema/types/connect/connect.graphql index eeb582a48..537b0b906 100644 --- a/api/src/graphql/schema/types/connect/connect.graphql +++ b/api/src/graphql/schema/types/connect/connect.graphql @@ -42,6 +42,8 @@ input SetupRemoteAccessInput { type Query { remoteAccess: RemoteAccess! extraAllowedOrigins: [String!]! + """ Temporary Type to Enable Swapping Dashboard over in Connect without major changes """ + serverDashboard: Dashboard! } type Mutation { diff --git a/api/src/graphql/schema/types/dashboard/dashboard.graphql b/api/src/graphql/schema/types/dashboard/dashboard.graphql new file mode 100644 index 000000000..ff4d7707e --- /dev/null +++ b/api/src/graphql/schema/types/dashboard/dashboard.graphql @@ -0,0 +1,93 @@ +type DashboardApps { + installed: Int + started: Int +} + +type DashboardVersions { + unraid: String +} + +type DashboardOs { + hostname: String + uptime: DateTime +} + +type DashboardVms { + installed: Int + started: Int +} + +type ArrayCapacityBytes { + free: Long + used: Long + total: Long +} + +type ArrayCapacity { + bytes: ArrayCapacityBytes +} + +type DashboardArray { + """ + Current array state + """ + state: String + """ + Current array capacity + """ + capacity: ArrayCapacity +} + +type DashboardCase { + icon: String + url: String + error: String + base64: String +} + +type DashboardDisplay { + case: DashboardCase +} + +type DashboardConfig { + valid: Boolean + error: String +} + +type DashboardVars { + regState: String + regTy: String + flashGuid: String + serverName: String + serverDescription: String +} + +type DashboardTwoFactorRemote { + enabled: Boolean +} + +type DashboardTwoFactorLocal { + enabled: Boolean +} + +type DashboardTwoFactor { + remote: DashboardTwoFactorRemote + local: DashboardTwoFactorLocal +} + +type Dashboard { + id: ID! + lastPublish: DateTime + online: Boolean + apps: DashboardApps + versions: DashboardVersions + os: DashboardOs + vms: DashboardVms + array: DashboardArray + services: [DashboardService] + display: DashboardDisplay + config: DashboardConfig + vars: DashboardVars + twoFactor: DashboardTwoFactor + network: Network +} diff --git a/api/src/graphql/schema/types/dashboard/network.graphql b/api/src/graphql/schema/types/dashboard/network.graphql new file mode 100644 index 000000000..591409673 --- /dev/null +++ b/api/src/graphql/schema/types/dashboard/network.graphql @@ -0,0 +1,33 @@ +enum URL_TYPE { + LAN + WIREGUARD + WAN + MDNS + DEFAULT +} + +type AccessUrl { + type: URL_TYPE! + name: String + ipv4: URL + ipv6: URL +} + +type Network { + accessUrls: [AccessUrl!] +} + +input AccessUrlInput { + type: URL_TYPE! + name: String + ipv4: URL + ipv6: URL +} + +input NetworkInput { + accessUrls: [AccessUrlInput!]! +} + +type Mutation { + updateNetwork(data: NetworkInput!): Network! +} diff --git a/api/src/graphql/schema/types/dashboard/service.graphql b/api/src/graphql/schema/types/dashboard/service.graphql new file mode 100644 index 000000000..9a05ff4fc --- /dev/null +++ b/api/src/graphql/schema/types/dashboard/service.graphql @@ -0,0 +1,22 @@ + +type DashboardServiceUptime { + timestamp: DateTime +} + +type DashboardService { + name: String + online: Boolean + uptime: DashboardServiceUptime + version: String +} + +input DashboardServiceUptimeInput { + timestamp: DateTime! +} + +input DashboardServiceInput { + name: String! + online: Boolean! + uptime: DashboardServiceUptimeInput + version: String! +} \ No newline at end of file diff --git a/api/src/graphql/schema/types/notifications/notifications.graphql b/api/src/graphql/schema/types/notifications/notifications.graphql index 9ad4911b4..0a794d63a 100644 --- a/api/src/graphql/schema/types/notifications/notifications.graphql +++ b/api/src/graphql/schema/types/notifications/notifications.graphql @@ -22,10 +22,6 @@ input NotificationFilter { limit: Int! } -type Mutation { - sendNotification(notification: NotificationInput!): Notification -} - type Query { notifications(filter: NotificationFilter!): [Notification!]! } diff --git a/api/src/mothership/subscribe-to-mothership.ts b/api/src/mothership/subscribe-to-mothership.ts index 178e74f13..49f1cb847 100644 --- a/api/src/mothership/subscribe-to-mothership.ts +++ b/api/src/mothership/subscribe-to-mothership.ts @@ -2,10 +2,6 @@ import { minigraphLogger, mothershipLogger } from '@app/core/log'; import { GraphQLClient } from './graphql-client'; import { store } from '@app/store'; -import { - startDashboardProducer, - stopDashboardProducer, -} from '@app/store/modules/dashboard'; import { EVENTS_SUBSCRIPTION, @@ -57,15 +53,6 @@ export const subscribeToEvents = async (apiKey: string) => { } } - // Dashboard Connected to Mothership - - if ( - type === ClientType.DASHBOARD && - apiKey === eventApiKey - ) { - store.dispatch(startDashboardProducer()); - } - break; } @@ -80,15 +67,6 @@ export const subscribeToEvents = async (apiKey: string) => { } } - // The dashboard was closed or went idle - - if ( - type === ClientType.DASHBOARD && - apiKey === eventApiKey - ) { - store.dispatch(stopDashboardProducer()); - } - break; } diff --git a/api/src/store/index.ts b/api/src/store/index.ts index 1ac013890..6c670df4a 100644 --- a/api/src/store/index.ts +++ b/api/src/store/index.ts @@ -5,7 +5,6 @@ import { configReducer } from '@app/store/modules/config'; import { emhttp } from '@app/store/modules/emhttp'; import { registration } from '@app/store/modules/registration'; import { cache } from '@app/store/modules/cache'; -import { dashboard } from '@app/store/modules/dashboard'; import { docker } from '@app/store/modules/docker'; import { upnp } from '@app/store/modules/upnp'; import { listenerMiddleware } from '@app/store/listeners/listener-middleware'; @@ -27,7 +26,6 @@ export const store = configureStore({ remoteGraphQL: remoteGraphQLReducer, notifications: notificationReducer, cache: cache.reducer, - dashboard: dashboard.reducer, docker: docker.reducer, upnp: upnp.reducer, dynamix: dynamix.reducer, @@ -45,7 +43,6 @@ export const getters = { apiKey: () => store.getState().apiKey, cache: () => store.getState().cache, config: () => store.getState().config, - dashboard: () => store.getState().dashboard, docker: () => store.getState().docker, dynamix: () => store.getState().dynamix, emhttp: () => store.getState().emhttp, diff --git a/api/src/unraid-api/graph/graph.module.ts b/api/src/unraid-api/graph/graph.module.ts index 84c8b121b..7ed9a8e7b 100644 --- a/api/src/unraid-api/graph/graph.module.ts +++ b/api/src/unraid-api/graph/graph.module.ts @@ -2,6 +2,7 @@ import { DateTimeResolver, JSONResolver, PortResolver, + URLResolver, UUIDResolver, } from 'graphql-scalars'; import { GraphQLLong } from '@app/graphql/resolvers/graphql-type-long'; @@ -42,6 +43,7 @@ import { print } from 'graphql'; UUID: UUIDResolver, DateTime: DateTimeResolver, Port: PortResolver, + URL: URLResolver }, // schema: schema }), diff --git a/api/src/unraid-api/graph/resolvers/cloud/cloud.resolver.ts b/api/src/unraid-api/graph/resolvers/cloud/cloud.resolver.ts index e6acb840b..436c3d2d6 100644 --- a/api/src/unraid-api/graph/resolvers/cloud/cloud.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/cloud/cloud.resolver.ts @@ -130,4 +130,5 @@ export class CloudResolver { await store.dispatch(setupRemoteAccessThunk(input)).unwrap(); return true; } + } diff --git a/api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.spec.ts b/api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.spec.ts new file mode 100644 index 000000000..70f9321d7 --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { DashboardResolver } from './dashboard.resolver'; + +describe('DashboardResolver', () => { + let resolver: DashboardResolver; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [DashboardResolver], + }).compile(); + + resolver = module.get(DashboardResolver); + }); + + it('should be defined', () => { + expect(resolver).toBeDefined(); + }); +}); diff --git a/api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.ts b/api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.ts new file mode 100644 index 000000000..080e8a7ff --- /dev/null +++ b/api/src/unraid-api/graph/resolvers/dashboard/dashboard.resolver.ts @@ -0,0 +1,38 @@ +import { dashboardDataServer } from '@app/common/dashboard/generate-server-data'; +import { Dashboard } from '@app/graphql/generated/api/types'; +import { getServerIps } from '@app/graphql/resolvers/subscription/network'; +import { Query, ResolveField, Resolver } from '@nestjs/graphql'; +import { UseRoles } from 'nest-access-control'; +import { ZodError } from 'zod'; + +@Resolver('Dashboard') +@UseRoles({ + resource: 'connect', + action: 'read', + possession: 'own', +}) +export class DashboardResolver { + @Query('serverDashboard') + public async serverDashboard(): Promise { + console.log('Dashboard is', await dashboardDataServer()); + return await dashboardDataServer(); + } + @ResolveField() + public network(): Dashboard['network'] { + const datapacket = getServerIps(); + if (datapacket.errors) { + const zodErrors = datapacket.errors.filter( + (error) => error instanceof ZodError + ); + if (zodErrors.length) { + console.warn( + 'Validation Errors Encountered with Network Payload: %s', + zodErrors.map((error) => error.message).join(',') + ); + } + } + const networkPacket: Dashboard['network'] = { accessUrls: datapacket.urls }; + + return networkPacket; + } +} diff --git a/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts b/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts index a338b23fc..ea56bf09a 100644 --- a/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/notifications/notifications.resolver.ts @@ -6,7 +6,6 @@ import { UseRoles } from 'nest-access-control'; import { Logger } from '@nestjs/common'; import { type NotificationInput } from '@app/graphql/generated/client/graphql'; import { GraphQLClient } from '@app/mothership/graphql-client'; -import { SEND_NOTIFICATION_MUTATION } from '@app/graphql/mothership/mutations'; import { PUBSUB_CHANNEL, createSubscription } from '@app/core/pubsub'; @Resolver() @@ -44,49 +43,6 @@ export class NotificationsResolver { .slice(offset, limit + offset); } - @Mutation('sendNotification') - @UseRoles({ - resource: 'notifications', - action: 'create', - possession: 'own', - }) - public async sendNotification( - @Args('notification') notification: NotificationInput - ) { - this.logger.log('Sending notification', JSON.stringify(notification)); - const promise = new Promise((res, rej) => { - setTimeout(async () => { - rej(new GraphQLError('Sending Notification Timeout')); - }, 5_000); - const client = GraphQLClient.getInstance(); - // If there's no mothership connection then bail - if (!client) { - this.logger.error('Mothership is not working'); - throw new GraphQLError('Mothership is down'); - } - client - .query({ - query: SEND_NOTIFICATION_MUTATION, - variables: { - notification: notification, - apiKey: getters.config().remote.apikey, - }, - }) - .then((result) => { - this.logger.debug( - 'Query Result from Notifications.ts', - result - ); - res(notification); - }) - .catch((err) => { - rej(err); - }); - }); - - return promise; - } - @Subscription('notificationAdded') @UseRoles({ resource: 'notifications', diff --git a/api/src/unraid-api/graph/resolvers/resolvers.module.ts b/api/src/unraid-api/graph/resolvers/resolvers.module.ts index 87a134a14..0993a7868 100644 --- a/api/src/unraid-api/graph/resolvers/resolvers.module.ts +++ b/api/src/unraid-api/graph/resolvers/resolvers.module.ts @@ -14,12 +14,14 @@ import { OwnerResolver } from './owner/owner.resolver'; import { RegistrationResolver } from './registration/registration.resolver'; import { ServerResolver } from './servers/server.resolver'; import { VarsResolver } from './vars/vars.resolver'; +import { DashboardResolver } from './dashboard/dashboard.resolver'; @Module({ providers: [ ArrayResolver, CloudResolver, ConfigResolver, + DashboardResolver, DisksResolver, DockerContainersResolver, DisplayResolver,