diff --git a/app/consts.ts b/app/consts.ts new file mode 100644 index 000000000..28c04e045 --- /dev/null +++ b/app/consts.ts @@ -0,0 +1,35 @@ +import { config } from '@unraid/core'; + +const internalWsAddress = () => { + const port = config.get('graphql-api-port'); + return isNaN(port as any) + // Unix Socket + ? `ws+unix:${port}` + // Numbered port + : `ws://localhost:${port}`; +} + +/** + * One second in milliseconds. + */ +export const ONE_SECOND = 1000; + +/** + * One minute in milliseconds. +*/ +export const ONE_MINUTE = 60 * ONE_SECOND; + +/** + * Relay ws link. + */ +export const MOTHERSHIP_RELAY_WS_LINK = process.env.RELAY_WS_LINK ? process.env.RELAY_WS_LINK : 'wss://mothership.unraid.net/relay'; + +/** + * Graphql link. + */ +export const MOTHERSHIP_GRAPHQL_LINK = process.env.RELAY_WS_LINK ? process.env.RELAY_WS_LINK : 'https://mothership.unraid.net/graphql'; + +/** + * Internal ws link. + */ +export const INTERNAL_WS_LINK = process.env.INTERNAL_WS_LINK ? process.env.INTERNAL_WS_LINK : internalWsAddress(); diff --git a/app/graphql/schema/resolvers.ts b/app/graphql/schema/resolvers.ts index c03d7cda0..6c84cb6f3 100644 --- a/app/graphql/schema/resolvers.ts +++ b/app/graphql/schema/resolvers.ts @@ -9,9 +9,11 @@ import { setIntervalAsync } from 'set-interval-async/dynamic'; import GraphQLJSON from 'graphql-type-json'; import GraphQLLong from 'graphql-type-long'; import GraphQLUUID from 'graphql-type-uuid'; +import fetch from 'cross-fetch'; import { run, publish } from '../../run'; import { userCache, CachedServer, CachedServers } from '../../cache'; -import { hasSubscribedToChannel, canPublishToChannel } from '../../ws'; +import { hasSubscribedToChannel } from '../../ws'; +import { MOTHERSHIP_GRAPHQL_LINK } from '../../consts'; const { ensurePermission } = utils; const { usersState, varState, networkState } = states; @@ -37,11 +39,6 @@ dee.on('*', async (data: { Type: string }) => { return; } - // Don't publish when we have no clients - if (!canPublishToChannel('info')) { - return; - } - const { json } = await modules.getApps(); publish('info', 'UPDATED', json); }); @@ -50,10 +47,6 @@ dee.listen(); // This needs to be fixed to run from events setIntervalAsync(async () => { - if (!canPublishToChannel('services')) { - return; - } - // @todo: Create a system user for this const user = usersState.findOne({ name: 'root' }); @@ -100,43 +93,67 @@ type makeNullUndefinedAndOptional = { type Server = makeNullUndefinedAndOptional; -const getServers = (): Server[] => { - const servers = userCache.get('mine')?.servers ?? []; +const getServers = async (): Promise => { + const cachedServers = userCache.get('mine')?.servers; - // Fall back to locally generated data - if (servers.length === 0) { - log.debug('Falling back to local state for /servers endpoint'); - const guid = varState?.data?.regGuid; - // For now use the my_servers key - // Later we should return the correct one for the current user with the correct scope, etc. - const apikey = apiManager.getValidKeys().find(key => key.name === 'my_servers')?.key.toString(); - const name = varState?.data?.name; - const wanip = null; - const lanip = networkState.data[0].ipaddr[0]; - const localurl = `http://${lanip}:${varState?.data?.port}`; - const remoteurl = null; - - return [{ - owner: { - username: 'root', - url: '', - avatar: '' + // No cached servers found + if (!cachedServers) { + // Fetch servers from mothership + const servers = await fetch(MOTHERSHIP_GRAPHQL_LINK, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + 'Accept': 'application/json', }, - guid, - apikey, - name, - status: 'online', - wanip, - lanip, - localurl, - remoteurl - }]; + body: JSON.stringify({ + query: "{ servers { owner { username url avatar } guid apikey name status wanip lanip localurl remoteurl } }" + }) + }).then(r => r.json() as Promise); + + log.debug('Using upstream for /servers endpoint'); + + // No servers found + if (servers.length === 0) { + return []; + } + + console.log({servers}); + + // @todo: Cache servers + // userCache.set('mine', { + // servers + // }) + + // Return servers from mothership + return servers; } - log.debug('Using upstream for /servers endpoint'); + log.debug('Falling back to local state for /servers endpoint'); + const guid = varState?.data?.regGuid; + // For now use the my_servers key + // Later we should return the correct one for the current user with the correct scope, etc. + const apikey = apiManager.getValidKeys().find(key => key.name === 'my_servers')?.key.toString(); + const name = varState?.data?.name; + const wanip = null; + const lanip = networkState.data[0].ipaddr[0]; + const localurl = `http://${lanip}:${varState?.data?.port}`; + const remoteurl = null; - // Return servers from upstream cache - return servers; + return [{ + owner: { + username: 'root', + url: '', + avatar: '' + }, + guid, + apikey, + name, + status: 'online', + wanip, + lanip, + localurl, + remoteurl + }]; }; export const resolvers = { @@ -152,7 +169,7 @@ export const resolvers = { }); // Single server - return getServers().find(server => server.name === name); + return getServers().then(server => server.find(server => server.name === name)); }, servers(_: unknown, __: unknown, context: Context) { ensurePermission(context.user, { diff --git a/app/mothership.ts b/app/mothership.ts index 5266090ba..d737bb78a 100644 --- a/app/mothership.ts +++ b/app/mothership.ts @@ -1,6 +1,7 @@ import fs from 'fs'; import WebSocket from 'ws'; -import { utils, paths, states, config } from '@unraid/core'; +import { utils, paths, states } from '@unraid/core'; +import { MOTHERSHIP_RELAY_WS_LINK, INTERNAL_WS_LINK } from './consts'; import { DynamixConfig } from '@unraid/core/dist/lib/types'; const log = console; @@ -11,34 +12,6 @@ const { varState } = states; process.on('uncaughtException', log.debug); process.on('unhandledRejection', log.debug); -const internalWsAddress = () => { - const port = config.get('graphql-api-port'); - return isNaN(port as any) - // Unix Socket - ? `ws+unix:${port}` - // Numbered port - : `ws://localhost:${port}`; -} - -/** - * One second in milliseconds. - */ -const ONE_SECOND = 1000; -/** - * One minute in milliseconds. -*/ -const ONE_MINUTE = 60 * ONE_SECOND; - -/** - * Relay ws link. - */ -const RELAY_WS_LINK = process.env.RELAY_WS_LINK ? process.env.RELAY_WS_LINK : 'wss://relay.unraid.net'; - -/** - * Internal ws link. - */ -const INTERNAL_WS_LINK = process.env.INTERNAL_WS_LINK ? process.env.INTERNAL_WS_LINK : internalWsAddress(); - /** * Get a number between the lowest and highest value. * @param low Lowest value. @@ -110,7 +83,7 @@ export const connectToMothership = async (wsServer: WebSocket.Server, currentRet // Connect to mothership's relay endpoint // Keep reference outside this scope so we can disconnect later - relay = new WebSocket(RELAY_WS_LINK, ['graphql-ws'], { + relay = new WebSocket(MOTHERSHIP_RELAY_WS_LINK, ['graphql-ws'], { headers: { 'x-api-key': apiKey, 'x-flash-guid': varState.data?.flashGuid ?? '', diff --git a/app/run.ts b/app/run.ts index 50b2ac231..d1c658577 100644 --- a/app/run.ts +++ b/app/run.ts @@ -1,6 +1,5 @@ import { CoreResult } from '@unraid/core/dist/lib/types'; import { pubsub, utils, log } from '@unraid/core'; -import { canPublishToChannel } from './ws'; const { debugTimer } = utils; @@ -19,11 +18,6 @@ export const publish = (channel: string, mutation: string, node?: {}) => { } }; - if (!canPublishToChannel(channel)) { - log.debug(`can't post to ${channel}`); - return; - } - // Update clients const fieldName = Object.keys(data)[0]; pubsub.publish(channel, { diff --git a/app/ws.ts b/app/ws.ts index 50dbd9b5e..f2f842556 100644 --- a/app/ws.ts +++ b/app/ws.ts @@ -1,5 +1,3 @@ -import { log } from '@unraid/core'; - interface subscription { total: number channels: string[] @@ -70,22 +68,3 @@ export const wsHasDisconnected = (id: string) => { subscriptions[id].total = 0; subscriptions[id].channels = []; }; - -// Only allows function to publish to pubsub when clients are online and are connected to the specific channel -// the reason we do this is otherwise pubsub will cause a memory leak -export const canPublishToChannel = (channel: string) => { - // No ws connections - if (getWsConectionCount() === 0) { - return false; - } - - // No ws connections to this channel - const channelConnectionCount = getWsConectionCountInChannel(channel); - if (channelConnectionCount === 0) { - return false; - } - - const plural = channelConnectionCount !== 1; - log.debug(`Allowing publish to "${channel}" as there ${plural ? 'are' : 'is'} ${channelConnectionCount} connection${plural ? 's' : ''} in that channel.`); - return true; -}; \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 01cbc99b6..08ed88c03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1223,7 +1223,7 @@ } }, "@unraid/core": { - "version": "github:unraid/core#f1569b3a663d73452c0aa9078e971a2788c4a208", + "version": "github:unraid/core#adeb036d27d4c0b00b96012b7e97eac0c783e4f9", "from": "github:unraid/core", "requires": { "accesscontrol": "^2.2.1", @@ -1273,6 +1273,7 @@ "path-exists": "^4.0.0", "path-type": "4.0.0", "pify": "^5.0.0", + "pretty-bytes": "^5.4.1", "pretty-ms": "^7.0.0", "redact-secrets": "github:omgimalexis/redact-secrets", "request": "^2.88.2", @@ -14349,6 +14350,11 @@ "fast-diff": "^1.1.2" } }, + "pretty-bytes": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", + "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==" + }, "pretty-ms": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.0.tgz", @@ -16388,9 +16394,9 @@ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==" }, "systeminformation": { - "version": "4.27.4", - "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.27.4.tgz", - "integrity": "sha512-VlFlxbkvSddq16F/nHC0GRaKBZOKWbAuRbck4G9muHhCUcDKskhNkVbaBBFxxqwcp0IyVozLS96eAVmkRZTG4w==" + "version": "4.27.5", + "resolved": "https://registry.npmjs.org/systeminformation/-/systeminformation-4.27.5.tgz", + "integrity": "sha512-EysogxKqREk54ZYDEFcsCODv8GymKZcyiSfegYit8dKhPjzuQr+KX4GFHjssWjYrWFEIM2bYNsFrZX5eufeAXg==" }, "table": { "version": "5.4.6",