mirror of
https://github.com/unraid/api.git
synced 2026-01-02 06:30:02 -06:00
fix: mothership connection, unneeded logging and servers endpoint
This commit is contained in:
35
app/consts.ts
Normal file
35
app/consts.ts
Normal file
@@ -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();
|
||||
@@ -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<T> = {
|
||||
|
||||
type Server = makeNullUndefinedAndOptional<CachedServer>;
|
||||
|
||||
const getServers = (): Server[] => {
|
||||
const servers = userCache.get<CachedServers>('mine')?.servers ?? [];
|
||||
const getServers = async (): Promise<Server[]> => {
|
||||
const cachedServers = userCache.get<CachedServers>('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<CachedServer[]>);
|
||||
|
||||
log.debug('Using upstream for /servers endpoint');
|
||||
|
||||
// No servers found
|
||||
if (servers.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
console.log({servers});
|
||||
|
||||
// @todo: Cache servers
|
||||
// userCache.set<CachedServers>('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, {
|
||||
|
||||
@@ -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 ?? '',
|
||||
|
||||
@@ -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, {
|
||||
|
||||
21
app/ws.ts
21
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;
|
||||
};
|
||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user