mirror of
https://github.com/unraid/api.git
synced 2026-01-15 05:00:16 -06:00
refactor(mothership): switch to new redis backed endpoints
This commit is contained in:
@@ -81,6 +81,7 @@ const baseTypes = [gql`
|
||||
ping: PingSubscription!
|
||||
info: InfoSubscription!
|
||||
pluginModule(plugin: String!, module: String!, params: JSON, result: String): PluginModuleSubscription!
|
||||
online: Boolean!
|
||||
}
|
||||
`];
|
||||
|
||||
@@ -182,8 +183,9 @@ const getPluginModule = (pluginName: string, pluginModuleName: string) => {
|
||||
*/
|
||||
class FuncDirective extends SchemaDirectiveVisitor {
|
||||
visitFieldDefinition(field: { [key: string]: any }) {
|
||||
// @ts-ignore
|
||||
const { args } = this;
|
||||
field.resolve = async function (source, directiveArgs: { [key: string]: any }, { user }, info: { [key: string]: any }) {
|
||||
field.resolve = async function (_source, directiveArgs: { [key: string]: any }, { user }, info: { [key: string]: any }) {
|
||||
const {module: moduleName, result: resultType} = args;
|
||||
const {plugin: pluginName, module: pluginModuleName, result: pluginType, input, ...params} = directiveArgs;
|
||||
const operationType = info.operation.operation;
|
||||
@@ -369,19 +371,25 @@ export const graphql = {
|
||||
wsHasDisconnected(websocketId);
|
||||
}
|
||||
},
|
||||
context: ({req, connection}) => {
|
||||
if (connection) {
|
||||
context: ({req, connection, ...args_}, ...args__) => {
|
||||
// Normal Websocket connection
|
||||
if (connection && Object.keys(connection.context).length >= 1) {
|
||||
// Check connection for metadata
|
||||
return {
|
||||
...connection.context
|
||||
};
|
||||
}
|
||||
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
const user = apiKeyToUser(apiKey);
|
||||
|
||||
return {
|
||||
user
|
||||
};
|
||||
// Normal HTTP connection
|
||||
if (req) {
|
||||
const apiKey = req.headers['x-api-key'];
|
||||
const user = apiKeyToUser(apiKey);
|
||||
return {
|
||||
user
|
||||
};
|
||||
}
|
||||
|
||||
console.log({req, connection, args_, args__});
|
||||
throw new Error('Invalid');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -48,6 +48,10 @@ dee.on('*', async (data: { Type: string }) => {
|
||||
|
||||
dee.listen();
|
||||
|
||||
setInterval(() => {
|
||||
publish('online', 'UPDATED', true);
|
||||
}, 1000);
|
||||
|
||||
// This needs to be fixed to run from events
|
||||
setIntervalAsync(async () => {
|
||||
if (!canPublishToChannel('services')) {
|
||||
@@ -232,6 +236,9 @@ export const resolvers = {
|
||||
hasSubscribedToChannel(context.websocketId, channel);
|
||||
return pubsub.asyncIterator(channel);
|
||||
}
|
||||
},
|
||||
online: {
|
||||
...createSubscription('online')
|
||||
}
|
||||
},
|
||||
JSON: GraphQLJSON,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import fs from 'fs';
|
||||
import request from 'request';
|
||||
import WebSocket from 'ws';
|
||||
import merge from 'deepmerge';
|
||||
import { log, utils, paths, states, config } from '@unraid/core';
|
||||
import { DynamixConfig } from '@unraid/core/dist/lib/types';
|
||||
import { userCache, CachedServer } from './cache';
|
||||
|
||||
const { loadState } = utils;
|
||||
const { varState } = states;
|
||||
|
||||
process.on('uncaughtException', console.log);
|
||||
process.on('unhandledRejection', console.log);
|
||||
|
||||
/**
|
||||
* One second in milliseconds.
|
||||
*/
|
||||
@@ -18,6 +18,16 @@ const ONE_SECOND = 1000;
|
||||
*/
|
||||
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 : `ws+unix://${config.get('graphql-api-port')}`;
|
||||
|
||||
/**
|
||||
* Get a number between the lowest and highest value.
|
||||
* @param low Lowest value.
|
||||
@@ -38,7 +48,7 @@ const backoff = (attempt: number, maxDelay: number, multiplier: number) => {
|
||||
return Math.round(Math.min(delay * multiplier, maxDelay));
|
||||
};
|
||||
|
||||
let mothership;
|
||||
let relay: WebSocket;
|
||||
|
||||
interface WebSocketWithHeartBeat extends WebSocket {
|
||||
pingTimeout?: NodeJS.Timeout
|
||||
@@ -56,78 +66,20 @@ function heartbeat(this: WebSocketWithHeartBeat) {
|
||||
this.pingTimeout = setTimeout(() => {
|
||||
this.terminate();
|
||||
}, 30000 + 1000);
|
||||
}
|
||||
|
||||
type MessageType = 'query' | 'mutation' | 'start' | 'stop' | 'proxy-data';
|
||||
interface Message {
|
||||
type: MessageType;
|
||||
payload: {
|
||||
operationName: any;
|
||||
variables: {};
|
||||
query: string;
|
||||
data: any;
|
||||
topic?: string;
|
||||
}
|
||||
};
|
||||
|
||||
type Server = CachedServer;
|
||||
type Servers = Server[];
|
||||
interface ProxyMessage extends Omit<Message, 'payload'> {
|
||||
type: MessageType
|
||||
payload: {
|
||||
topic: 'servers';
|
||||
data: Servers;
|
||||
}
|
||||
};
|
||||
const readFileIfExists = (filePath: string) => {
|
||||
try {
|
||||
return fs.readFileSync(filePath);
|
||||
} catch {}
|
||||
|
||||
const isProxyMessage = (message: any): message is ProxyMessage => {
|
||||
const keys = Object.keys(message.payload ?? {});
|
||||
return message.payload && message.type === 'proxy-data' && keys.length === 2 && keys.includes('topic') && keys.includes('data');
|
||||
};
|
||||
|
||||
const isServersPayload = (payload: any): payload is Servers => payload.topic === 'servers';
|
||||
|
||||
const forwardMessageToLocalSocket = (message: Message, apiKey: string) => {
|
||||
log.debug(`Got a "${message.type}" request from mothership, forwarding to socket.`);
|
||||
const port = config.get('graphql-api-port');
|
||||
const localEndpoint = (!isNaN(port as number)) ? `localhost:${port}` : `unix:${port}:`;
|
||||
const url = `http://${localEndpoint}/graphql`;
|
||||
request.post(url, {
|
||||
body: JSON.stringify({
|
||||
operationName: null,
|
||||
variables: {},
|
||||
query: message.payload.query
|
||||
}),
|
||||
headers: {
|
||||
Accept: '*/*',
|
||||
'Content-Type': 'application/json',
|
||||
'x-api-key': apiKey
|
||||
}
|
||||
}, (error, response) => {
|
||||
if (error) {
|
||||
log.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const data = JSON.parse(response.body).data;
|
||||
const payload = { data };
|
||||
log.debug('Replying to mothership with payload %o', payload);
|
||||
mothership.send(JSON.stringify({
|
||||
type: 'data',
|
||||
payload
|
||||
}));
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
mothership.close();
|
||||
}
|
||||
});
|
||||
return Buffer.from('');
|
||||
};
|
||||
|
||||
/**
|
||||
* Connect to unraid's proxy server
|
||||
*/
|
||||
export const connectToMothership = async (wsServer, currentRetryAttempt: number = 0) => {
|
||||
export const connectToMothership = async (wsServer: WebSocket.Server, currentRetryAttempt: number = 0) => {
|
||||
// Kill the last connection first
|
||||
await disconnectFromMothership();
|
||||
let retryAttempt = currentRetryAttempt;
|
||||
@@ -137,14 +89,15 @@ export const connectToMothership = async (wsServer, currentRetryAttempt: number
|
||||
}
|
||||
|
||||
const apiKey = loadState<DynamixConfig>(paths.get('dynamix-config')!).remote.apikey || '';
|
||||
const keyFile = varState.data?.regFile ? fs.readFileSync(varState.data?.regFile).toString('base64') : '';
|
||||
const keyFile = varState.data?.regFile ? readFileIfExists(varState.data?.regFile).toString('base64') : '';
|
||||
const serverName = `${varState.data?.name}`;
|
||||
const lanIp = states.networkState.data.find(network => network.ipaddr[0]).ipaddr[0] || '';
|
||||
const machineId = `${await utils.getMachineId()}`;
|
||||
let localGraphqlApi: WebSocket;
|
||||
|
||||
// Connect to mothership
|
||||
// Connect to mothership's relay endpoint
|
||||
// Keep reference outside this scope so we can disconnect later
|
||||
mothership = new WebSocket('wss://proxy.unraid.net', ['graphql-ws'], {
|
||||
relay = new WebSocket(RELAY_WS_LINK, ['graphql-ws'], {
|
||||
headers: {
|
||||
'x-api-key': apiKey,
|
||||
'x-flash-guid': varState.data?.flashGuid ?? '',
|
||||
@@ -155,84 +108,118 @@ export const connectToMothership = async (wsServer, currentRetryAttempt: number
|
||||
}
|
||||
});
|
||||
|
||||
mothership.on('open', function() {
|
||||
log.debug('Connected to mothership.');
|
||||
relay.on('open', async () => {
|
||||
log.debug(`Connected to mothership's relay.`);
|
||||
|
||||
// Reset retry attempts
|
||||
retryAttempt = 0;
|
||||
|
||||
// Connect mothership to the internal ws server
|
||||
wsServer.emit('connection', mothership);
|
||||
// Connect to the internal graphql server
|
||||
localGraphqlApi = new WebSocket(INTERNAL_WS_LINK, ['graphql-ws']);
|
||||
|
||||
// Start ping/pong
|
||||
// @ts-ignore
|
||||
heartbeat.bind(this);
|
||||
// Heartbeat
|
||||
localGraphqlApi.on('ping', () => {
|
||||
heartbeat.bind(localGraphqlApi)();
|
||||
});
|
||||
|
||||
// Errors
|
||||
localGraphqlApi.on('error', error => {
|
||||
log.error('ws:local-relay', 'error', error);
|
||||
});
|
||||
|
||||
// Connection to local graphql endpoint is "closed"
|
||||
localGraphqlApi.on('close', () => {
|
||||
log.debug('ws:local-relay', 'close');
|
||||
});
|
||||
|
||||
// Connection to local graphql endpoint is "open"
|
||||
localGraphqlApi.on('open', () => {
|
||||
log.debug('ws:local-relay', 'open');
|
||||
|
||||
// Authenticate with ourselves
|
||||
localGraphqlApi.send(JSON.stringify({
|
||||
type: 'connection_init',
|
||||
payload: {
|
||||
'x-api-key': apiKey
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
// Relay message back to mothership
|
||||
localGraphqlApi.on('message', (data) => {
|
||||
try {
|
||||
relay.send(data);
|
||||
} catch (error) {
|
||||
// Relay socket is closed, close internal one
|
||||
if (error.message.includes('WebSocket is not open')) {
|
||||
localGraphqlApi.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
mothership.on('close', async function (this: WebSocketWithHeartBeat) {
|
||||
if (this.pingTimeout) {
|
||||
clearTimeout(this.pingTimeout);
|
||||
|
||||
// Relay is closed
|
||||
relay.on('close', async function (this: WebSocketWithHeartBeat, ...args) {
|
||||
try {
|
||||
log.debug('Connection closed.', ...args);
|
||||
|
||||
if (this.pingTimeout) {
|
||||
clearTimeout(this.pingTimeout);
|
||||
}
|
||||
|
||||
// Clear all listeners before running this again
|
||||
relay?.removeAllListeners();
|
||||
|
||||
// Close connection to local graphql endpoint
|
||||
localGraphqlApi?.close();
|
||||
|
||||
// Reconnect
|
||||
setTimeout(async () => {
|
||||
await connectToMothership(wsServer, retryAttempt + 1);
|
||||
}, backoff(retryAttempt, ONE_MINUTE, 5));
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
|
||||
// Clear all listeners before running this again
|
||||
mothership?.removeAllListeners();
|
||||
|
||||
// Reconnect
|
||||
setTimeout(async () => {
|
||||
await connectToMothership(wsServer, retryAttempt + 1);
|
||||
}, backoff(retryAttempt, ONE_MINUTE, 2));
|
||||
});
|
||||
|
||||
mothership.on('error', error => {
|
||||
// Mothership is down
|
||||
relay.on('error', (error: NodeJS.ErrnoException) => {
|
||||
// The relay is down
|
||||
if (error.message.includes('502')) {
|
||||
return;
|
||||
}
|
||||
|
||||
log.error(error.message);
|
||||
// Connection refused, aka couldn't connect
|
||||
// This is usually because the address is wrong or offline
|
||||
if (error.code === 'ECONNREFUSED') {
|
||||
// @ts-expect-error
|
||||
log.debug(`Couldn't connect to ${error.address}:${error.port}`);
|
||||
return;
|
||||
}
|
||||
|
||||
log.error(error);
|
||||
});
|
||||
|
||||
mothership.on('ping', heartbeat);
|
||||
relay.on('ping', heartbeat);
|
||||
|
||||
mothership.on('message', async (stringifiedData: string) => {
|
||||
const sendMessage = (client, message, timeout = 1000) => {
|
||||
try {
|
||||
const message: Message = JSON.parse(stringifiedData);
|
||||
|
||||
// Proxy this to the http endpoint
|
||||
if (message.type === 'query' || message.type === 'mutation') {
|
||||
forwardMessageToLocalSocket(message, apiKey);
|
||||
if (client.readyState === 0) {
|
||||
setTimeout(() => {
|
||||
sendMessage(client, message, timeout);
|
||||
log.debug('Message sent to mothership.', message)
|
||||
}, timeout);
|
||||
return;
|
||||
}
|
||||
|
||||
log.debug(`Got a "${message.type}" request from mothership, handling internally.`);
|
||||
client.send(message);
|
||||
} catch (error) {
|
||||
log.error('Failed replying to mothership.', error);
|
||||
};
|
||||
};
|
||||
|
||||
if (isProxyMessage(message)) {
|
||||
const payload = message.payload;
|
||||
|
||||
if (isServersPayload(payload)) {
|
||||
const cachedData = userCache.get<CachedServer[]>('mine');
|
||||
const newData = {
|
||||
servers: payload.data
|
||||
};
|
||||
|
||||
// If we don't have cached data just save this
|
||||
if (!cachedData || cachedData.length === 0) {
|
||||
userCache.set('mine', newData);
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop all new servers and merge new data on top of the cached stuff
|
||||
// This should mean { guid: "1", status: "offline" } should keep
|
||||
// all data but update the "status" field.
|
||||
const mergedData = {
|
||||
servers: newData.servers.map(newServer => {
|
||||
const cachedServer = cachedData?.find(cachedServer => cachedServer.guid === newServer.guid);
|
||||
return cachedServer ? merge(cachedServer, newServer) : newServer;
|
||||
})
|
||||
};
|
||||
|
||||
userCache.set('mine', mergedData);
|
||||
}
|
||||
}
|
||||
relay.on('message', async (data: string) => {
|
||||
try {
|
||||
sendMessage(localGraphqlApi, data);
|
||||
} catch (error) {
|
||||
// Something weird happened while processing the message
|
||||
// This is likely a malformed message
|
||||
@@ -245,11 +232,10 @@ export const connectToMothership = async (wsServer, currentRetryAttempt: number
|
||||
* Disconnect from mothership.
|
||||
*/
|
||||
export const disconnectFromMothership = async () => {
|
||||
if (mothership && mothership.readyState !== 0) {
|
||||
if (relay && relay.readyState !== 0) {
|
||||
log.debug('Disconnecting from the proxy server.');
|
||||
try {
|
||||
mothership.close();
|
||||
mothership = undefined;
|
||||
relay.close();
|
||||
} catch {}
|
||||
}
|
||||
};
|
||||
@@ -20,11 +20,15 @@ export const publish = (channel: string, mutation: string, node?: {}) => {
|
||||
};
|
||||
|
||||
if (!canPublishToChannel(channel)) {
|
||||
// console.log(`can't post to ${channel}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update clients
|
||||
pubsub.publish(channel, data);
|
||||
const fieldName = Object.keys(data)[0];
|
||||
pubsub.publish(channel, {
|
||||
[fieldName]: data[fieldName].node
|
||||
});
|
||||
};
|
||||
|
||||
interface RunOptions {
|
||||
|
||||
@@ -30,7 +30,7 @@ const ONE_SECOND = 1000;
|
||||
const app = express();
|
||||
const port = String(config.get('graphql-api-port'));
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
app.use(async (_req, res, next) => {
|
||||
// Only get the machine ID on first request
|
||||
// We do this to avoid using async in the main server function
|
||||
if (!app.get('x-machine-id')) {
|
||||
|
||||
18
app/ws.ts
18
app/ws.ts
@@ -24,11 +24,27 @@ export const getWsConectionCountInChannel = (channel: string) => {
|
||||
};
|
||||
|
||||
export const hasSubscribedToChannel = (id: string, channel: string) => {
|
||||
// Setup inital object
|
||||
if (subscriptions[id] === undefined) {
|
||||
subscriptions[id] = {
|
||||
total: 1,
|
||||
channels: [channel]
|
||||
};
|
||||
return;
|
||||
}
|
||||
subscriptions[id].total++;
|
||||
subscriptions[id].channels.push(channel);
|
||||
};
|
||||
|
||||
export const hasUnsubscribedFromChannel = (id: string, channel: string) => {
|
||||
// Setup inital object
|
||||
if (subscriptions[id] === undefined) {
|
||||
subscriptions[id] = {
|
||||
total: 0,
|
||||
channels: []
|
||||
};
|
||||
return;
|
||||
}
|
||||
subscriptions[id].total--;
|
||||
subscriptions[id].channels = subscriptions[id].channels.filter(existingChannel => existingChannel !== channel);
|
||||
};
|
||||
@@ -60,12 +76,14 @@ export const wsHasDisconnected = (id: string) => {
|
||||
export const canPublishToChannel = (channel: string) => {
|
||||
// No ws connections
|
||||
if (getWsConectionCount() === 0) {
|
||||
// log.debug('No ws connections, cannot publish');
|
||||
return false;
|
||||
}
|
||||
|
||||
// No ws connections to this channel
|
||||
const channelConnectionCount = getWsConectionCountInChannel(channel);
|
||||
if (channelConnectionCount === 0) {
|
||||
// log.debug(`No connections to channel ${channel}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
82
package-lock.json
generated
82
package-lock.json
generated
@@ -4,6 +4,40 @@
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
"@apollo/client": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/client/-/client-3.1.3.tgz",
|
||||
"integrity": "sha512-zXMiaj+dX0sgXIwEV5d/PI6B8SZT2bqlKNjZWcEXRY7NjESF5J3nd4v8KOsrhHe+A3YhNv63tIl35Sq7uf41Pg==",
|
||||
"requires": {
|
||||
"@types/zen-observable": "^0.8.0",
|
||||
"@wry/context": "^0.5.2",
|
||||
"@wry/equality": "^0.2.0",
|
||||
"fast-json-stable-stringify": "^2.0.0",
|
||||
"graphql-tag": "^2.11.0",
|
||||
"hoist-non-react-statics": "^3.3.2",
|
||||
"optimism": "^0.12.1",
|
||||
"prop-types": "^15.7.2",
|
||||
"symbol-observable": "^1.2.0",
|
||||
"ts-invariant": "^0.4.4",
|
||||
"tslib": "^1.10.0",
|
||||
"zen-observable": "^0.8.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@wry/equality": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.2.0.tgz",
|
||||
"integrity": "sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"graphql-tag": {
|
||||
"version": "2.11.0",
|
||||
"resolved": "https://registry.npmjs.org/graphql-tag/-/graphql-tag-2.11.0.tgz",
|
||||
"integrity": "sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"@apollo/protobufjs": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@apollo/protobufjs/-/protobufjs-1.0.4.tgz",
|
||||
@@ -1090,6 +1124,11 @@
|
||||
"integrity": "sha512-1jmXgoIyzxQSm33lYgEXvegtkhloHbed2I0QGlTN66U2F9/ExqJWSCSmaWC0IB/g1tW+IYSp+tDhcZBYB1ZGog==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/zen-observable": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.0.tgz",
|
||||
"integrity": "sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg=="
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-3.6.0.tgz",
|
||||
@@ -1264,6 +1303,14 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@wry/context": {
|
||||
"version": "0.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@wry/context/-/context-0.5.2.tgz",
|
||||
"integrity": "sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw==",
|
||||
"requires": {
|
||||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@wry/equality": {
|
||||
"version": "0.1.11",
|
||||
"resolved": "https://registry.npmjs.org/@wry/equality/-/equality-0.1.11.tgz",
|
||||
@@ -5783,6 +5830,14 @@
|
||||
"warning": "^4.0.3"
|
||||
}
|
||||
},
|
||||
"cross-fetch": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.0.5.tgz",
|
||||
"integrity": "sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew==",
|
||||
"requires": {
|
||||
"node-fetch": "2.6.0"
|
||||
}
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
@@ -9654,6 +9709,14 @@
|
||||
"minimalistic-crypto-utils": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"hoist-non-react-statics": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
|
||||
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
|
||||
"requires": {
|
||||
"react-is": "^16.7.0"
|
||||
}
|
||||
},
|
||||
"homedir-polyfill": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz",
|
||||
@@ -11057,7 +11120,6 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"js-tokens": "^3.0.0 || ^4.0.0"
|
||||
}
|
||||
@@ -13569,6 +13631,14 @@
|
||||
"integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==",
|
||||
"dev": true
|
||||
},
|
||||
"optimism": {
|
||||
"version": "0.12.1",
|
||||
"resolved": "https://registry.npmjs.org/optimism/-/optimism-0.12.1.tgz",
|
||||
"integrity": "sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ==",
|
||||
"requires": {
|
||||
"@wry/context": "^0.5.2"
|
||||
}
|
||||
},
|
||||
"optional": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/optional/-/optional-0.1.4.tgz",
|
||||
@@ -14290,7 +14360,6 @@
|
||||
"version": "15.7.2",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz",
|
||||
"integrity": "sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.4.0",
|
||||
"object-assign": "^4.1.1",
|
||||
@@ -14531,8 +14600,7 @@
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"dev": true
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"react-popper": {
|
||||
"version": "1.3.7",
|
||||
@@ -16198,9 +16266,9 @@
|
||||
}
|
||||
},
|
||||
"subscriptions-transport-ws": {
|
||||
"version": "0.9.16",
|
||||
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz",
|
||||
"integrity": "sha512-pQdoU7nC+EpStXnCfh/+ho0zE0Z+ma+i7xvj7bkXKb1dvYHSZxgRPaU6spRP+Bjzow67c/rRDoix5RT0uU9omw==",
|
||||
"version": "0.9.17",
|
||||
"resolved": "https://registry.npmjs.org/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.17.tgz",
|
||||
"integrity": "sha512-hNHi2N80PBz4T0V0QhnnsMGvG3XDFDS9mS6BhZ3R12T6EBywC8d/uJscsga0cVO4DKtXCkCRrWm2sOYrbOdhEA==",
|
||||
"requires": {
|
||||
"backo2": "^1.0.2",
|
||||
"eventemitter3": "^3.1.0",
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"index.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.1.3",
|
||||
"@gridplus/docker-events": "github:OmgImAlexis/docker-events",
|
||||
"@unraid/core": "github:unraid/core",
|
||||
"accesscontrol": "^2.2.1",
|
||||
@@ -36,6 +37,7 @@
|
||||
"apollo-server": "2.14.2",
|
||||
"apollo-server-express": "2.14.2",
|
||||
"camelcase": "6.0.0",
|
||||
"cross-fetch": "^3.0.5",
|
||||
"dot-prop": "^5.2.0",
|
||||
"express": "^4.17.1",
|
||||
"graphql": "^15.1.0",
|
||||
@@ -51,7 +53,7 @@
|
||||
"redact-secrets": "github:omgimalexis/redact-secrets",
|
||||
"set-interval-async": "^1.0.33",
|
||||
"stoppable": "^1.1.0",
|
||||
"subscriptions-transport-ws": "^0.9.16"
|
||||
"subscriptions-transport-ws": "^0.9.17"
|
||||
},
|
||||
"optionalDependencies": {},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -9,10 +9,10 @@
|
||||
"skipLibCheck": true,
|
||||
/* Basic Options */
|
||||
// "incremental": true, /* Enable incremental compilation */
|
||||
"target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"target": "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */
|
||||
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
|
||||
// "lib": [], /* Specify library files to be included in the compilation. */
|
||||
"allowJs": true, /* Allow javascript files to be compiled. */
|
||||
"allowJs": false, /* Allow javascript files to be compiled. */
|
||||
// "checkJs": true, /* Report errors in .js files. */
|
||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||
@@ -23,7 +23,7 @@
|
||||
"rootDir": "./app", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
// "composite": true, /* Enable project compilation */
|
||||
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||
// "removeComments": true, /* Do not emit comments to output. */
|
||||
"removeComments": true, /* Do not emit comments to output. */
|
||||
// "noEmit": true, /* Do not emit outputs. */
|
||||
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||
@@ -40,10 +40,10 @@
|
||||
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
/* Module Resolution Options */
|
||||
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||
|
||||
Reference in New Issue
Block a user