mirror of
https://github.com/unraid/api.git
synced 2026-01-17 22:20:06 -06:00
chore: lint
This commit is contained in:
7
.eslintrc.js
Normal file
7
.eslintrc.js
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = {
|
||||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
extends: [
|
||||
'@unraid'
|
||||
],
|
||||
};
|
||||
2
app/cache/index.ts
vendored
2
app/cache/index.ts
vendored
@@ -1 +1 @@
|
||||
export * from './user';
|
||||
export * from './user';
|
||||
|
||||
32
app/cache/user.ts
vendored
32
app/cache/user.ts
vendored
@@ -7,23 +7,23 @@ type IpAddress = string;
|
||||
type Status = 'online' | 'offline';
|
||||
|
||||
export interface Owner {
|
||||
username: string;
|
||||
url: URL;
|
||||
avatar: URL;
|
||||
};
|
||||
username: string;
|
||||
url: URL;
|
||||
avatar: URL;
|
||||
}
|
||||
|
||||
export interface CachedServer {
|
||||
owner: Owner;
|
||||
guid: string;
|
||||
apikey: string;
|
||||
name: string;
|
||||
status: Status;
|
||||
wanip: IpAddress;
|
||||
lanip: IpAddress;
|
||||
localurl: URL;
|
||||
remoteurl: string
|
||||
};
|
||||
owner: Owner;
|
||||
guid: string;
|
||||
apikey: string;
|
||||
name: string;
|
||||
status: Status;
|
||||
wanip: IpAddress;
|
||||
lanip: IpAddress;
|
||||
localurl: URL;
|
||||
remoteurl: string;
|
||||
}
|
||||
|
||||
export interface CachedServers {
|
||||
servers: CachedServer[]
|
||||
};
|
||||
servers: CachedServer[];
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { config } from './core/config';
|
||||
|
||||
const internalWsAddress = () => {
|
||||
const port = config.get('port');
|
||||
return isNaN(port as any)
|
||||
const port = config.get('port') as number | string;
|
||||
return isNaN(port as any) ?
|
||||
// Unix Socket
|
||||
? `ws+unix:${port}`
|
||||
`ws+unix:${port}` :
|
||||
// Numbered port
|
||||
: `ws://localhost:${port}`;
|
||||
}
|
||||
`ws://localhost:${port}`;
|
||||
};
|
||||
|
||||
/**
|
||||
* One second in milliseconds.
|
||||
|
||||
@@ -9,7 +9,6 @@ import { EventEmitter } from 'events';
|
||||
import toMillisecond from 'ms';
|
||||
import dotProp from 'dot-prop';
|
||||
import { Cache as MemoryCache } from 'clean-cache';
|
||||
// @ts-ignore
|
||||
import { validate as validateArgument } from 'bycontract';
|
||||
import { Mutex, MutexInterface } from 'async-mutex';
|
||||
import { validateApiKeyFormat, loadState, validateApiKey } from './utils';
|
||||
@@ -53,7 +52,7 @@ export class ApiManager extends EventEmitter {
|
||||
|
||||
/** Note: Keys expire by default after 365 days. */
|
||||
private readonly keys = new MemoryCache<CacheItem>(Number(toMillisecond('1y')));
|
||||
|
||||
|
||||
private lock?: MutexInterface;
|
||||
private async getLock() {
|
||||
if (!this.lock) {
|
||||
@@ -70,11 +69,11 @@ export class ApiManager extends EventEmitter {
|
||||
const lock = await this.getLock();
|
||||
try {
|
||||
const file = loadState<{ remote: { apikey: string } }>(filePath);
|
||||
const apiKey = dotProp.get(file, 'remote.apikey') as string;
|
||||
const apiKey = dotProp.get(file, 'remote.apikey')! as string;
|
||||
|
||||
// Same key as current
|
||||
if (!force && (apiKey === this.getKey('my_servers')?.key)) {
|
||||
coreLogger.debug(`%s was updated but the API key didn't change`, filePath);
|
||||
coreLogger.debug('%s was updated but the API key didn\'t change', filePath);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,12 +135,12 @@ export class ApiManager extends EventEmitter {
|
||||
|
||||
/**
|
||||
* Replace a key.
|
||||
*
|
||||
*
|
||||
* Note: This will bump the expiration by the original length.
|
||||
*/
|
||||
replace(name: string, key: string, options: KeyOptions) {
|
||||
// Delete existing key
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
this.keys.items[name] = null;
|
||||
|
||||
// Add new key
|
||||
@@ -269,7 +268,7 @@ export class ApiManager extends EventEmitter {
|
||||
.filter(([, item]) => this.isValid(item.value.key))
|
||||
.map(([name, item]) => ({
|
||||
name,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
key: item.value.key,
|
||||
userId: item.value.userId,
|
||||
expiresAt: item.expiresAt
|
||||
@@ -288,7 +287,7 @@ export class ApiManager extends EventEmitter {
|
||||
|
||||
const keyObject = Object
|
||||
.entries(this.keys.items)
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
.find(([_, item]) => item.value.key === key);
|
||||
|
||||
if (!keyObject) {
|
||||
|
||||
@@ -40,7 +40,7 @@ const loadingLogger = (namespace: string): void => {
|
||||
/**
|
||||
* Register state paths.
|
||||
*/
|
||||
const loadStatePaths = async(): Promise<void> => {
|
||||
const loadStatePaths = async (): Promise<void> => {
|
||||
const statesCwd = paths.get('states')!;
|
||||
const cwd = path.join(__dirname, 'states');
|
||||
|
||||
@@ -52,10 +52,10 @@ const loadStatePaths = async(): Promise<void> => {
|
||||
const filePath = `${path.join(statesCwd, state)}.ini`;
|
||||
|
||||
// Don't override already set paths
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
if (!paths.has(name)) {
|
||||
// ['state:Users', '/usr/local/emhttp/state/users.ini']
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
paths.set(name, filePath);
|
||||
}
|
||||
});
|
||||
@@ -64,7 +64,7 @@ const loadStatePaths = async(): Promise<void> => {
|
||||
/**
|
||||
* Register all plugins with PluginManager.
|
||||
*/
|
||||
const loadPlugins = async(): Promise<void> => {
|
||||
const loadPlugins = async (): Promise<void> => {
|
||||
// Bail in safe mode
|
||||
if (config.get('safe-mode')) {
|
||||
coreLogger.debug('No plugins have been loaded as you\'re in SAFE MODE');
|
||||
@@ -114,7 +114,7 @@ const loadPlugins = async(): Promise<void> => {
|
||||
/**
|
||||
* Start all watchers.
|
||||
*/
|
||||
const loadWatchers = async(): Promise<void> => {
|
||||
const loadWatchers = async (): Promise<void> => {
|
||||
if (config.get('safe-mode')) {
|
||||
coreLogger.debug('Skipping loading watchers');
|
||||
return;
|
||||
@@ -136,7 +136,7 @@ const loadWatchers = async(): Promise<void> => {
|
||||
* @async
|
||||
* @private
|
||||
*/
|
||||
const loadApiKeys = async(): Promise<void> => {
|
||||
const loadApiKeys = async (): Promise<void> => {
|
||||
// @TODO: For each key in a json file load them
|
||||
};
|
||||
|
||||
@@ -145,7 +145,7 @@ const loadApiKeys = async(): Promise<void> => {
|
||||
*
|
||||
* @param endpoints
|
||||
*/
|
||||
const connectToNchanEndpoints = async(endpoints: string[]): Promise<void> => {
|
||||
const connectToNchanEndpoints = async (endpoints: string[]): Promise<void> => {
|
||||
coreLogger.debug('Connected to nchan, setting-up endpoints.');
|
||||
const connections = endpoints.map(async endpoint => subscribeToNchanEndpoint(endpoint));
|
||||
await Promise.all(connections);
|
||||
@@ -158,7 +158,7 @@ const connectToNchanEndpoints = async(endpoints: string[]): Promise<void> => {
|
||||
* @async
|
||||
* @private
|
||||
*/
|
||||
const loadNchan = async(): Promise<void> => {
|
||||
const loadNchan = async (): Promise<void> => {
|
||||
const endpoints = ['devs', 'disks', 'sec', 'sec_nfs', 'shares', 'users', 'var'];
|
||||
|
||||
coreLogger.debug('Trying to connect to nchan');
|
||||
@@ -169,7 +169,7 @@ const loadNchan = async(): Promise<void> => {
|
||||
interval: ONE_SECOND
|
||||
})
|
||||
// Once connected open a connection to each known endpoint
|
||||
.then(async() => connectToNchanEndpoints(endpoints))
|
||||
.then(async () => connectToNchanEndpoints(endpoints))
|
||||
.catch(error => {
|
||||
// Nchan is likely unreachable
|
||||
if (error.message.includes('Promise timed out')) {
|
||||
@@ -196,7 +196,7 @@ const loaders = {
|
||||
*
|
||||
* @name core.load
|
||||
*/
|
||||
const load = async(): Promise<void> => {
|
||||
const load = async (): Promise<void> => {
|
||||
await loadStatePaths();
|
||||
await loadPlugins();
|
||||
await loadWatchers();
|
||||
@@ -214,7 +214,7 @@ const load = async(): Promise<void> => {
|
||||
* @name core.loadServer
|
||||
* @param name The name of the server instance to load.
|
||||
*/
|
||||
export const loadServer = async(name: string, server: typeof Server): Promise<void> => {
|
||||
export const loadServer = async (name: string, server: typeof Server): Promise<void> => {
|
||||
// Set process title
|
||||
process.title = name;
|
||||
|
||||
@@ -232,7 +232,7 @@ export const loadServer = async(name: string, server: typeof Server): Promise<vo
|
||||
});
|
||||
|
||||
// On process exit
|
||||
exitHook(async() => {
|
||||
exitHook(async () => {
|
||||
// Only do this when there's a TTY present
|
||||
if (process.stdout.isTTY) {
|
||||
// Ensure we go back to the start of the line
|
||||
@@ -241,7 +241,7 @@ export const loadServer = async(name: string, server: typeof Server): Promise<vo
|
||||
}
|
||||
|
||||
coreLogger.debug('Stopping server');
|
||||
|
||||
|
||||
// Stop the server
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
@@ -1,62 +1,62 @@
|
||||
export const admin = {
|
||||
extends: 'user',
|
||||
permissions: [
|
||||
// @NOTE: Uncomment the first line to enable creation of api keys.
|
||||
// See the README.md for more information.
|
||||
// @WARNING: This is currently unsupported, please be careful.
|
||||
// { resource: 'apikey', action: 'create:any', attributes: '*' },
|
||||
{ resource: 'apikey', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'array', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'cpu', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'device', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'device/unassigned', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'disk', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'disk/settings', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'display', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'docker/container', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'docker/network', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'info', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'license-key', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'machine-id', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'memory', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'online', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'os', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'parity-history', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'permission', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'plugin', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'servers', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'service', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'service/emhttpd', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'service/unraid-api', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'services', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'share', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'software-versions', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'unraid-version', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'uptime', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'user', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'var', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vars', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vm/domain', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vm/network', action: 'read:any', attributes: '*' },
|
||||
]
|
||||
extends: 'user',
|
||||
permissions: [
|
||||
// @NOTE: Uncomment the first line to enable creation of api keys.
|
||||
// See the README.md for more information.
|
||||
// @WARNING: This is currently unsupported, please be careful.
|
||||
// { resource: 'apikey', action: 'create:any', attributes: '*' },
|
||||
{ resource: 'apikey', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'array', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'cpu', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'device', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'device/unassigned', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'disk', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'disk/settings', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'display', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'docker/container', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'docker/network', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'info', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'license-key', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'machine-id', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'memory', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'online', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'os', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'parity-history', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'permission', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'plugin', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'servers', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'service', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'service/emhttpd', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'service/unraid-api', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'services', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'share', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'software-versions', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'unraid-version', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'uptime', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'user', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'var', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vars', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vm/domain', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vm/network', action: 'read:any', attributes: '*' }
|
||||
]
|
||||
};
|
||||
|
||||
export const user = {
|
||||
extends: 'guest',
|
||||
permissions: [
|
||||
{ resource: 'apikey', action: 'read:own', attributes: '*' },
|
||||
extends: 'guest',
|
||||
permissions: [
|
||||
{ resource: 'apikey', action: 'read:own', attributes: '*' },
|
||||
{ resource: 'permission', action: 'read:any', attributes: '*' }
|
||||
]
|
||||
]
|
||||
};
|
||||
|
||||
export const guest = {
|
||||
permissions: [
|
||||
{ resource: 'welcome', action: 'read:any', attributes: '*' }
|
||||
]
|
||||
{ resource: 'welcome', action: 'read:any', attributes: '*' }
|
||||
]
|
||||
};
|
||||
|
||||
export const permissions = {
|
||||
admin,
|
||||
user,
|
||||
guest
|
||||
}
|
||||
admin,
|
||||
user,
|
||||
guest
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { AppError } from '../errors';
|
||||
/**
|
||||
* Announce to the local network via mDNS.
|
||||
*/
|
||||
export const announce = async(): Promise<void> => {
|
||||
export const announce = async (): Promise<void> => {
|
||||
const name = varState.data?.name;
|
||||
const localTld = varState.data?.localTld;
|
||||
const version = varState.data?.version;
|
||||
|
||||
@@ -9,7 +9,7 @@ import { AppError } from './app-error';
|
||||
* API key error.
|
||||
*/
|
||||
export class ApiKeyError extends AppError {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Logger } from 'logger';
|
||||
|
||||
export const log = new Logger({ prefix: '@unraid' });
|
||||
export const coreLogger = log.createChild({ prefix: 'core' });
|
||||
export const mothershipLogger = log.createChild({ prefix: 'mothership'});
|
||||
export const graphqlLogger = log.createChild({ prefix: 'graphql'});
|
||||
export const relayLogger = log.createChild({ prefix: 'relay'});
|
||||
export const discoveryLogger = log.createChild({ prefix: 'discovery'});
|
||||
export const mothershipLogger = log.createChild({ prefix: 'mothership' });
|
||||
export const graphqlLogger = log.createChild({ prefix: 'graphql' });
|
||||
export const relayLogger = log.createChild({ prefix: 'relay' });
|
||||
export const discoveryLogger = log.createChild({ prefix: 'discovery' });
|
||||
|
||||
@@ -27,7 +27,7 @@ interface Result extends CoreResult {
|
||||
*
|
||||
* NOTE: If the name or key is missing they'll be generated.
|
||||
*/
|
||||
export const addApikey = async(context: Context): Promise<Result | void> => {
|
||||
export const addApikey = async (context: Context): Promise<Result | void> => {
|
||||
ensurePermission(context.user, {
|
||||
resource: 'apikey',
|
||||
action: 'create',
|
||||
|
||||
@@ -31,16 +31,16 @@ interface Result extends CoreResult {
|
||||
/**
|
||||
* Register a license key.
|
||||
*/
|
||||
export const addLicenseKey = async(context: Context): Promise<Result | void> => {
|
||||
export const addLicenseKey = async (context: Context): Promise<Result | void> => {
|
||||
ensurePermission(context.user, {
|
||||
resource: 'license-key',
|
||||
action: 'create',
|
||||
possession: 'any'
|
||||
});
|
||||
|
||||
// const { data } = context;
|
||||
// Const { data } = context;
|
||||
const guid = varState?.data?.regGuid;
|
||||
// const timestamp = new Date();
|
||||
// Const timestamp = new Date();
|
||||
|
||||
if (!guid) {
|
||||
throw new AppError('guid missing');
|
||||
|
||||
@@ -25,7 +25,7 @@ interface Context extends CoreContext {
|
||||
* Install plugin.
|
||||
* @returns The newly installed plugin.
|
||||
*/
|
||||
export const addPlugin = async(context: Context): Promise<CoreResult> => {
|
||||
export const addPlugin = async (context: Context): Promise<CoreResult> => {
|
||||
// Check permissions
|
||||
ensurePermission(context.user, {
|
||||
resource: 'plugin',
|
||||
|
||||
@@ -8,7 +8,7 @@ import { AppError, NotImplementedError } from '../errors';
|
||||
import { sharesState, slotsState } from '../states';
|
||||
import { ensurePermission } from '../utils';
|
||||
|
||||
export const addShare = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const addShare = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user, data = {} } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -23,7 +23,7 @@ interface Context extends CoreContext {
|
||||
/**
|
||||
* Add user account.
|
||||
*/
|
||||
export const addUser = async(context: Context): Promise<CoreResult> => {
|
||||
export const addUser = async (context: Context): Promise<CoreResult> => {
|
||||
const { data } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -19,6 +19,6 @@ interface Context extends CoreContext {
|
||||
* Invalidate an apiKey.
|
||||
* @returns The deleted apikey.
|
||||
*/
|
||||
export const deleteApikey = async(_: Context): Promise<CoreResult> => {
|
||||
export const deleteApikey = async (_: Context): Promise<CoreResult> => {
|
||||
throw new NotImplementedError();
|
||||
};
|
||||
|
||||
@@ -40,7 +40,7 @@ interface Context extends CoreContext {
|
||||
* @param {string} context.params.name
|
||||
* @returns {Core~Result} The API key, the user who owns the key, when the key expires and the scopes the key can use.
|
||||
*/
|
||||
export const getApikey = async(context: Context): Promise<Result> => {
|
||||
export const getApikey = async (context: Context): Promise<Result> => {
|
||||
const { params, user } = context;
|
||||
const { name } = params;
|
||||
|
||||
@@ -76,7 +76,7 @@ export const getApikey = async(context: Context): Promise<Result> => {
|
||||
const apiKey = apiManager.getKey(name)?.key;
|
||||
|
||||
if (!apiKey) {
|
||||
throw new AppError(`A key under this name hasn't been issued or it has expired.`);
|
||||
throw new AppError('A key under this name hasn\'t been issued or it has expired.');
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -21,7 +21,7 @@ interface Context extends CoreContext {
|
||||
*
|
||||
* @returns The apikey, when the key expires and the scopes the key can use.
|
||||
*/
|
||||
export const updateApiKey = async(context: Context): Promise<CoreResult> => {
|
||||
export const updateApiKey = async (context: Context): Promise<CoreResult> => {
|
||||
// Since we pass the context we don't need to worry about checking if the user has permissions
|
||||
const key = await getApikey(context).then(result => result.json);
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getArray } from '..';
|
||||
/**
|
||||
* Add a disk to the array.
|
||||
*/
|
||||
export const addDiskToArray = async function(context: CoreContext): Promise<CoreResult> {
|
||||
export const addDiskToArray = async function (context: CoreContext): Promise<CoreResult> {
|
||||
const { data = {}, user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -19,7 +19,7 @@ interface Context extends CoreContext {
|
||||
* Remove a disk from the array.
|
||||
* @returns The updated array.
|
||||
*/
|
||||
export const removeDiskFromArray = async(context: Context): Promise<CoreResult> => {
|
||||
export const removeDiskFromArray = async (context: Context): Promise<CoreResult> => {
|
||||
const { data, user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -13,7 +13,7 @@ import { getArray } from '..';
|
||||
// ideally this should have a timeout to prevent it sticking
|
||||
let locked = false;
|
||||
|
||||
export const updateArray = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const updateArray = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { data = {}, user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -12,9 +12,9 @@ type State = 'start' | 'cancel' | 'resume' | 'cancel';
|
||||
|
||||
interface Context extends CoreContext {
|
||||
data: {
|
||||
state?: State
|
||||
correct?: boolean
|
||||
}
|
||||
state?: State;
|
||||
correct?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -16,7 +16,7 @@ interface Context extends CoreContext {
|
||||
/**
|
||||
* Get a single disk.
|
||||
*/
|
||||
export const getDisk = async(context: Context, Disks): Promise<CoreResult> => {
|
||||
export const getDisk = async (context: Context, Disks): Promise<CoreResult> => {
|
||||
const { params, user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -19,7 +19,7 @@ interface Context extends CoreContext {
|
||||
* Get all Docker containers.
|
||||
* @returns All the in/active Docker containers on the system.
|
||||
*/
|
||||
export const getDockerContainers = async(context: Context): Promise<CoreResult> => {
|
||||
export const getDockerContainers = async (context: Context): Promise<CoreResult> => {
|
||||
const { query, user } = context;
|
||||
const { all } = query;
|
||||
|
||||
@@ -52,7 +52,7 @@ export const getDockerContainers = async(context: Context): Promise<CoreResult>
|
||||
.map(object => camelCaseKeys(object, { deep: true }))
|
||||
.map(container => {
|
||||
// This will be fixed once camelCaseKeys has correct typings
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const names = container.names[0];
|
||||
return {
|
||||
...container,
|
||||
|
||||
@@ -7,7 +7,7 @@ import camelCaseKeys from 'camelcase-keys';
|
||||
import { docker, catchHandlers, ensurePermission } from '../../utils';
|
||||
import { CoreContext, CoreResult } from '../../types';
|
||||
|
||||
export const getDockerNetworks = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getDockerNetworks = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -9,7 +9,7 @@ import { getShares, ensurePermission } from '../utils';
|
||||
/**
|
||||
* Get all shares.
|
||||
*/
|
||||
export const getAllShares = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getAllShares = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -12,7 +12,7 @@ import { CoreResult, CoreContext } from '../types';
|
||||
*
|
||||
* @returns All apikeys with their respective `name`, `key` and `expiresAt`.
|
||||
*/
|
||||
export const getApikeys = async function(context: CoreContext): Promise<CoreResult> {
|
||||
export const getApikeys = async function (context: CoreContext): Promise<CoreResult> {
|
||||
const { user } = context;
|
||||
const canReadAny = checkPermission(user, {
|
||||
resource: 'apikey',
|
||||
|
||||
@@ -8,7 +8,7 @@ import { CoreResult } from '../types';
|
||||
/**
|
||||
* Get all apps.
|
||||
*/
|
||||
export const getApps = async(): Promise<CoreResult> => {
|
||||
export const getApps = async (): Promise<CoreResult> => {
|
||||
const apps = [];
|
||||
|
||||
return {
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ensurePermission } from '../utils';
|
||||
* Get all devices.
|
||||
* @returns All currently connected devices.
|
||||
*/
|
||||
export const getDevices = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getDevices = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -22,7 +22,7 @@ interface Disk extends si.Systeminformation.DiskLayoutData {
|
||||
partitions: Partition[];
|
||||
}
|
||||
|
||||
const getTemperature = async(disk: si.Systeminformation.DiskLayoutData): Promise<number> => {
|
||||
const getTemperature = async (disk: si.Systeminformation.DiskLayoutData): Promise<number> => {
|
||||
const stdout = await execa('smartctl', ['-A', disk.device]).then(({ stdout }) => stdout).catch(() => '');
|
||||
const lines = stdout.split('\n');
|
||||
const header = lines.find(line => line.startsWith('ID#'))!;
|
||||
@@ -43,7 +43,7 @@ const getTemperature = async(disk: si.Systeminformation.DiskLayoutData): Promise
|
||||
return Number.parseInt(line[line.length - 1], 10);
|
||||
};
|
||||
|
||||
const parseDisk = async(disk: si.Systeminformation.DiskLayoutData, partitionsToParse: si.Systeminformation.BlockDevicesData[]): Promise<Disk> => {
|
||||
const parseDisk = async (disk: si.Systeminformation.DiskLayoutData, partitionsToParse: si.Systeminformation.BlockDevicesData[]): Promise<Disk> => {
|
||||
const partitions = partitionsToParse
|
||||
// Only get partitions from this disk
|
||||
.filter(partition => partition.name.startsWith(disk.device.split('/dev/')[1]))
|
||||
@@ -70,7 +70,7 @@ interface Result extends CoreResult {
|
||||
/**
|
||||
* Get all disks.
|
||||
*/
|
||||
export const getDisks = async(context: CoreContext): Promise<Result> => {
|
||||
export const getDisks = async (context: CoreContext): Promise<Result> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -15,7 +15,7 @@ const Table = require('cli-table');
|
||||
* Get parity history.
|
||||
* @returns All parity checks with their respective date, duration, speed, status and errors.
|
||||
*/
|
||||
export const getParityHistory = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getParityHistory = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Bail if the user doesn't have permission
|
||||
|
||||
@@ -10,7 +10,7 @@ import { CoreContext, CoreResult } from '../types';
|
||||
/**
|
||||
* Get all permissions.
|
||||
*/
|
||||
export const getPermissions = async function(context: CoreContext): Promise<CoreResult> {
|
||||
export const getPermissions = async function (context: CoreContext): Promise<CoreResult> {
|
||||
const { user } = context;
|
||||
|
||||
// Bail if the user doesn't have permission
|
||||
@@ -22,7 +22,7 @@ export const getPermissions = async function(context: CoreContext): Promise<Core
|
||||
|
||||
// Get all scopes
|
||||
const scopes = Object.assign({}, ...Object.values(ac.getGrants()).map(grant => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const { $extend, ...grants } = grant;
|
||||
return {
|
||||
...grants,
|
||||
@@ -33,7 +33,7 @@ export const getPermissions = async function(context: CoreContext): Promise<Core
|
||||
// Get all roles and their scopes
|
||||
const grants = Object.entries(ac.getGrants())
|
||||
.map(([name, grant]) => {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const { $extend, ...grants } = grant;
|
||||
return [name, grants];
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ interface Context extends CoreContext {
|
||||
}
|
||||
|
||||
interface Result extends CoreResult {
|
||||
json: Plugin[]
|
||||
json: Plugin[];
|
||||
}
|
||||
|
||||
export const getPlugins = (context: Readonly<Context>): Result => {
|
||||
|
||||
@@ -46,7 +46,7 @@ interface Result extends CoreResult {
|
||||
/**
|
||||
* Get all services.
|
||||
*/
|
||||
export const getServices = async(context: CoreContext): Promise<Result> => {
|
||||
export const getServices = async (context: CoreContext): Promise<Result> => {
|
||||
const logErrorAndReturnEmptyArray = (error: Error) => {
|
||||
coreLogger.error(error);
|
||||
return [];
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ensurePermission } from '../utils';
|
||||
/**
|
||||
* Get all unassigned devices.
|
||||
*/
|
||||
export const getUnassignedDevices = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getUnassignedDevices = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Bail if the user doesn't have permission
|
||||
|
||||
@@ -11,14 +11,14 @@ interface Result extends CoreResult {
|
||||
json: {
|
||||
milliseconds: number;
|
||||
timestamp: string;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* OS uptime
|
||||
* @returns The milliseconds since we booted.
|
||||
*/
|
||||
export const getUptime = async(context: CoreContext): Promise<Result> => {
|
||||
export const getUptime = async (context: CoreContext): Promise<Result> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -19,7 +19,7 @@ interface Context extends CoreContext {
|
||||
/**
|
||||
* Get all users.
|
||||
*/
|
||||
export const getUsers = async(context: Context): Promise<CoreResult> => {
|
||||
export const getUsers = async (context: Context): Promise<CoreResult> => {
|
||||
const { query, user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ensurePermission } from '../utils';
|
||||
/**
|
||||
* Get all system vars.
|
||||
*/
|
||||
export const getVars = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getVars = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Bail if the user doesn't have permission
|
||||
|
||||
@@ -11,7 +11,7 @@ import { getUnraidVersion } from '.';
|
||||
* Get welcome message.
|
||||
* @returns Welcomes a user.
|
||||
*/
|
||||
export const getWelcome = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getWelcome = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Bail if the user doesn't have permission
|
||||
|
||||
@@ -88,7 +88,7 @@ const systemPciDevices = async (): Promise<PciDevice[]> => {
|
||||
*/
|
||||
const processedDevices = await filterDevices(filteredDevices).then(devices => {
|
||||
return devices
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
.map(addDeviceClass)
|
||||
.map(device => {
|
||||
// Attempt to get the current kernel-bound driver for this pci device
|
||||
@@ -140,7 +140,7 @@ const systemAudioDevices = systemPciDevices().then(devices => {
|
||||
* System usb devices.
|
||||
* @returns Array of USB devices.
|
||||
*/
|
||||
const getSystemUSBDevices = async(): Promise<any[]> => {
|
||||
const getSystemUSBDevices = async (): Promise<any[]> => {
|
||||
// Get a list of all usb hubs so we can filter the allowed/disallowed
|
||||
const usbHubs = await execa('cat /sys/bus/usb/drivers/hub/*/modalias', { shell: true }).then(({ stdout }) => {
|
||||
return stdout.split('\n').map(line => {
|
||||
@@ -153,7 +153,7 @@ const getSystemUSBDevices = async(): Promise<any[]> => {
|
||||
const filterBootDrive = (device: Readonly<PciDevice>): boolean => varState?.data?.flashGuid !== device.guid;
|
||||
|
||||
// Remove usb hubs
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const filterUsbHubs = (device: Readonly<PciDevice>): boolean => !usbHubs.includes(device.id);
|
||||
|
||||
// Clean up the name
|
||||
@@ -165,7 +165,7 @@ const getSystemUSBDevices = async(): Promise<any[]> => {
|
||||
};
|
||||
};
|
||||
|
||||
const parseDeviceLine = (line: Readonly<string>): { value: string, string: string } => {
|
||||
const parseDeviceLine = (line: Readonly<string>): { value: string; string: string } => {
|
||||
const emptyLine = { value: '', string: '' };
|
||||
|
||||
// If the line is blank return nothing
|
||||
@@ -231,7 +231,7 @@ const getSystemUSBDevices = async(): Promise<any[]> => {
|
||||
}) || [];
|
||||
|
||||
// Get all usb devices
|
||||
const usbDevices = await execa('lsusb').then(async({ stdout }) => {
|
||||
const usbDevices = await execa('lsusb').then(async ({ stdout }) => {
|
||||
return parseUsbDevices(stdout)
|
||||
.map(parseDevice)
|
||||
.filter(filterBootDrive)
|
||||
@@ -245,7 +245,7 @@ const getSystemUSBDevices = async(): Promise<any[]> => {
|
||||
/**
|
||||
* Get device info.
|
||||
*/
|
||||
export const getAllDevices = async function(context: Readonly<CoreContext>): Promise<CoreResult> {
|
||||
export const getAllDevices = async function (context: Readonly<CoreContext>): Promise<CoreResult> {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -24,7 +24,7 @@ interface Result extends CoreResult {
|
||||
/**
|
||||
* Get count of docker containers
|
||||
*/
|
||||
export const getAppCount = async function(context: Readonly<CoreContext>): Promise<Result> {
|
||||
export const getAppCount = async function (context: Readonly<CoreContext>): Promise<Result> {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -7,7 +7,7 @@ import si from 'systeminformation';
|
||||
import { CoreContext, CoreResult } from '../../types';
|
||||
import { ensurePermission } from '../../utils';
|
||||
|
||||
export const getBaseboard = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getBaseboard = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ensurePermission } from '../../utils';
|
||||
/**
|
||||
* Get CPU info.
|
||||
*/
|
||||
export const getCpu = async function(context: CoreContext): Promise<CoreResult> {
|
||||
export const getCpu = async function (context: CoreContext): Promise<CoreResult> {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -28,7 +28,7 @@ const toBoolean = (prop: string): boolean => prop.toLowerCase() === 'true';
|
||||
/**
|
||||
* Get display info.
|
||||
*/
|
||||
export const getDisplay = async function(context: CoreContext): Promise<Result> {
|
||||
export const getDisplay = async function (context: CoreContext): Promise<Result> {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -9,7 +9,7 @@ import { ensurePermission, getMachineId as getMachineIdFromFile } from '../../ut
|
||||
/**
|
||||
* Get the machine ID.
|
||||
*/
|
||||
export const getMachineId = async function(context: CoreContext): Promise<CoreResult> {
|
||||
export const getMachineId = async function (context: CoreContext): Promise<CoreResult> {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -13,7 +13,7 @@ import { cleanStdout, ensurePermission } from '../../utils';
|
||||
/**
|
||||
* Get memory.
|
||||
*/
|
||||
export const getMemory = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getMemory = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ensurePermission } from '../../utils';
|
||||
* @memberof Core
|
||||
* @module info/get-os
|
||||
*/
|
||||
export const getOs = async function(context: CoreContext): Promise<CoreResult> {
|
||||
export const getOs = async function (context: CoreContext): Promise<CoreResult> {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -14,7 +14,7 @@ const cache = new CacheManager('unraid:modules:get-system-versions');
|
||||
* Software versions.
|
||||
* @returns Versions of all the core software.
|
||||
*/
|
||||
export const getSoftwareVersions = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getSoftwareVersions = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -15,15 +15,15 @@ const cache = new CacheManager('unraid:modules:get-unraid-version');
|
||||
|
||||
interface Result extends CoreResult {
|
||||
json: {
|
||||
unraid: string
|
||||
}
|
||||
unraid: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unraid version string.
|
||||
* @returns The current version.
|
||||
*/
|
||||
export const getUnraidVersion = async(context: CoreContext): Promise<Result> => {
|
||||
export const getUnraidVersion = async (context: CoreContext): Promise<Result> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -9,7 +9,7 @@ import { CoreResult, CoreContext } from '../../types';
|
||||
/**
|
||||
* Get all version info.
|
||||
*/
|
||||
export const getVersions = async function(context: CoreContext): Promise<CoreResult> {
|
||||
export const getVersions = async function (context: CoreContext): Promise<CoreResult> {
|
||||
const unraidVersion = await getUnraidVersion(context).then(result => result.json);
|
||||
const softwareVersions = await getSoftwareVersions(context).then(result => result.json);
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Result extends CoreResult {
|
||||
/**
|
||||
* Get emhttpd service info.
|
||||
*/
|
||||
export const getEmhttpdService = async(context: CoreContext): Promise<Result> => {
|
||||
export const getEmhttpdService = async (context: CoreContext): Promise<Result> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -9,13 +9,13 @@ import { CoreContext, CoreResult } from '../../types';
|
||||
const namespace = 'unraid-api';
|
||||
|
||||
interface Result extends CoreResult {
|
||||
json: NodeService
|
||||
json: NodeService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Unraid api service info.
|
||||
*/
|
||||
export const getUnraidApiService = async(context: CoreContext): Promise<Result> => {
|
||||
export const getUnraidApiService = async (context: CoreContext): Promise<Result> => {
|
||||
const service = await getNodeService(context.user, namespace);
|
||||
return {
|
||||
text: `Service: ${JSON.stringify(service, null, 2)}`,
|
||||
|
||||
@@ -26,7 +26,7 @@ interface Result extends CoreResult {
|
||||
/**
|
||||
* Update disk settings.
|
||||
*/
|
||||
export const updateDisk = async(context: Context): Promise<Result> => {
|
||||
export const updateDisk = async (context: Context): Promise<Result> => {
|
||||
const { data, user } = context;
|
||||
|
||||
// Check permissions
|
||||
@@ -43,7 +43,7 @@ export const updateDisk = async(context: Context): Promise<Result> => {
|
||||
* @param allowedValues Which values which are allowed.
|
||||
* @param optional If the value can also be undefined.
|
||||
*/
|
||||
const check = (property: string, allowedValues: { [key: string]: string } | string[], optional = true): void => {
|
||||
const check = (property: string, allowedValues: Record<string, string> | string[], optional = true): void => {
|
||||
const value = data[property];
|
||||
|
||||
// Skip checking if the value isn't needed and it's not set
|
||||
|
||||
@@ -21,7 +21,7 @@ interface Result extends CoreResult {
|
||||
/**
|
||||
* Get single share.
|
||||
*/
|
||||
export const getShare = async function(context: Context): Promise<Result> {
|
||||
export const getShare = async function (context: Context): Promise<Result> {
|
||||
const { params, user } = context;
|
||||
const { name } = params;
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ interface Context extends CoreContext {
|
||||
/**
|
||||
* Add role to user.
|
||||
*/
|
||||
export const addRole = async(context: Context): Promise<CoreResult> => {
|
||||
export const addRole = async (context: Context): Promise<CoreResult> => {
|
||||
const { user, params } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -18,7 +18,7 @@ interface Context extends CoreContext {
|
||||
/**
|
||||
* Delete user account.
|
||||
*/
|
||||
export const deleteUser = async(context: Context): Promise<CoreResult> => {
|
||||
export const deleteUser = async (context: Context): Promise<CoreResult> => {
|
||||
// Check permissions
|
||||
ensurePermission(context.user, {
|
||||
resource: 'user',
|
||||
|
||||
@@ -20,7 +20,7 @@ interface Context extends CoreContext {
|
||||
* Get single user.
|
||||
* @returns The selected user.
|
||||
*/
|
||||
export const getUser = async(context: Context): Promise<CoreResult> => {
|
||||
export const getUser = async (context: Context): Promise<CoreResult> => {
|
||||
// Check permissions
|
||||
ensurePermission(context.user, {
|
||||
resource: 'user',
|
||||
|
||||
@@ -17,7 +17,7 @@ interface Context extends CoreContext {
|
||||
/**
|
||||
* Get a single vm domain.
|
||||
*/
|
||||
export const getDomain = async function(context: Context): Promise<CoreResult> {
|
||||
export const getDomain = async function (context: Context): Promise<CoreResult> {
|
||||
const { params, user } = context;
|
||||
const { name } = params;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { parseDomains, getHypervisor, ensurePermission } from '../../utils';
|
||||
/**
|
||||
* Get vm domains.
|
||||
*/
|
||||
export const getDomains = async(context: CoreContext): Promise<CoreResult> => {
|
||||
export const getDomains = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
|
||||
@@ -8,12 +8,12 @@ import { log } from '../log';
|
||||
import { Notifier, NotifierOptions, NotifierSendOptions } from './notifier';
|
||||
|
||||
interface Options extends NotifierOptions {
|
||||
to: string
|
||||
from?: string
|
||||
replyTo?: string
|
||||
to: string;
|
||||
from?: string;
|
||||
replyTo?: string;
|
||||
}
|
||||
|
||||
interface SendOptions extends NotifierSendOptions {};
|
||||
interface SendOptions extends NotifierSendOptions {}
|
||||
|
||||
/**
|
||||
* Email notifer
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import fetch from 'node-fetch';
|
||||
import { Notifier, NotifierOptions } from './notifier';
|
||||
|
||||
export interface Options extends NotifierOptions {};
|
||||
export interface Options extends NotifierOptions {}
|
||||
|
||||
/**
|
||||
* HTTP notifier.
|
||||
|
||||
@@ -10,7 +10,7 @@ export type NotifierLevel = 'info' | 'error' | 'debug';
|
||||
|
||||
export interface NotifierOptions {
|
||||
level: NotifierLevel;
|
||||
helpers?: object;
|
||||
helpers?: Record<string, unknown>;
|
||||
template?: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { NotifierSendOptions } from './notifier';
|
||||
type Transport = 'email' | 'push' | 'ios' | 'android';
|
||||
|
||||
interface Options extends HttpNotifierOptions {
|
||||
transport?: Transport
|
||||
transport?: Transport;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Written by: Alexis Tyler
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
import { validate as validateArgument } from 'bycontract';
|
||||
import { LooseObject } from './types';
|
||||
import { AppError } from './errors';
|
||||
@@ -72,7 +72,7 @@ class PermissionManager {
|
||||
};
|
||||
|
||||
// Update the known list of valid scopes
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
this.knownScopes = [
|
||||
...this.knownScopes,
|
||||
...Object.values(scopeObject)
|
||||
|
||||
@@ -10,14 +10,12 @@ interface Permission {
|
||||
resource: string;
|
||||
action: string;
|
||||
attributes: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface Permissions {
|
||||
[roleName: string]: {
|
||||
extends?: string | string[];
|
||||
permissions?: Permission[];
|
||||
};
|
||||
};
|
||||
type Permissions = Record<string, {
|
||||
extends?: string | string[];
|
||||
permissions?: Permission[];
|
||||
}>;
|
||||
|
||||
const loadPermissionsFile = (filePath: string) => {
|
||||
// Create file if it's missing
|
||||
|
||||
@@ -188,7 +188,7 @@ export class PluginManager {
|
||||
}
|
||||
|
||||
// Initialize plugin
|
||||
await Promise.resolve(plugin.init(context, core)).then(async() => {
|
||||
await Promise.resolve(plugin.init(context, core)).then(async () => {
|
||||
coreLogger.debug('Plugin "%s" loaded successfully.', pluginName);
|
||||
|
||||
// Add to manager
|
||||
|
||||
@@ -11,39 +11,37 @@ import { toBoolean } from '../utils/casting';
|
||||
import { parseConfig } from '../utils/misc';
|
||||
import { ArrayState } from './state';
|
||||
|
||||
interface NetworkIni {
|
||||
[key: string]: {
|
||||
dhcpKeepresolv: IniStringBoolean;
|
||||
dnsServer1: string;
|
||||
dnsServer2: string;
|
||||
dhcp6Keepresolv: IniStringBoolean;
|
||||
bonding: IniStringBoolean;
|
||||
bondname: string;
|
||||
bondnics: CommaSeparatedString;
|
||||
bondingMode: string;
|
||||
bondingMiimon: string;
|
||||
bridging: IniStringBoolean;
|
||||
brname: string;
|
||||
brnics: string;
|
||||
brstp: string;
|
||||
brfd: string;
|
||||
'description:0': string;
|
||||
'protocol:0': string;
|
||||
'useDhcp:0': IniStringBoolean;
|
||||
'ipaddr:0': string;
|
||||
'netmask:0': string;
|
||||
'gateway:0': string;
|
||||
'metric:0': string;
|
||||
'useDhcp6:0': IniStringBoolean;
|
||||
'ipaddr6:0': string;
|
||||
'netmask6:0': string;
|
||||
'gateway6:0': string;
|
||||
'metric6:0': string;
|
||||
'privacy6:0': string;
|
||||
mtu: string;
|
||||
type: string;
|
||||
};
|
||||
}
|
||||
type NetworkIni = Record<string, {
|
||||
dhcpKeepresolv: IniStringBoolean;
|
||||
dnsServer1: string;
|
||||
dnsServer2: string;
|
||||
dhcp6Keepresolv: IniStringBoolean;
|
||||
bonding: IniStringBoolean;
|
||||
bondname: string;
|
||||
bondnics: CommaSeparatedString;
|
||||
bondingMode: string;
|
||||
bondingMiimon: string;
|
||||
bridging: IniStringBoolean;
|
||||
brname: string;
|
||||
brnics: string;
|
||||
brstp: string;
|
||||
brfd: string;
|
||||
'description:0': string;
|
||||
'protocol:0': string;
|
||||
'useDhcp:0': IniStringBoolean;
|
||||
'ipaddr:0': string;
|
||||
'netmask:0': string;
|
||||
'gateway:0': string;
|
||||
'metric:0': string;
|
||||
'useDhcp6:0': IniStringBoolean;
|
||||
'ipaddr6:0': string;
|
||||
'netmask6:0': string;
|
||||
'gateway6:0': string;
|
||||
'metric6:0': string;
|
||||
'privacy6:0': string;
|
||||
mtu: string;
|
||||
type: string;
|
||||
}>;
|
||||
|
||||
const parse = (state: NetworkIni) => {
|
||||
return Object.values(state).map(network => {
|
||||
|
||||
@@ -92,7 +92,7 @@ class SmbSec extends ArrayState {
|
||||
return data;
|
||||
}
|
||||
|
||||
find(query?: {}): any[] {
|
||||
find(query?: Record<string, unknown>): any[] {
|
||||
return super.find(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,8 @@ type Mutation = 'CREATED' | 'UPDATED' | 'DELETED';
|
||||
|
||||
export class State {
|
||||
channel?: string;
|
||||
_data?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
_data?: Record<string, any>;
|
||||
|
||||
lastUpdated: Date;
|
||||
|
||||
constructor() {
|
||||
@@ -42,7 +41,9 @@ export class State {
|
||||
const channel = this.channel;
|
||||
|
||||
// Bail since we have no channel to post to
|
||||
if (!channel) return;
|
||||
if (!channel) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update channel with new state
|
||||
bus.emit(channel, {
|
||||
|
||||
@@ -5,13 +5,9 @@ import { User } from './states';
|
||||
*/
|
||||
export type CommaSeparatedString = string;
|
||||
|
||||
export interface LooseObject {
|
||||
[key: string]: any;
|
||||
}
|
||||
export type LooseObject = Record<string, any>;
|
||||
|
||||
export interface LooseStringObject {
|
||||
[key: string]: string;
|
||||
}
|
||||
export type LooseStringObject = Record<string, string>;
|
||||
|
||||
/**
|
||||
* Context object
|
||||
@@ -20,9 +16,9 @@ export interface LooseStringObject {
|
||||
* @property param Params object.
|
||||
*/
|
||||
export interface CoreContext {
|
||||
readonly query?: Readonly<{ [key: string]: any }>;
|
||||
readonly data?: Readonly<{ [key: string]: any }>;
|
||||
readonly params?: Readonly<{ [key: string]: string }>;
|
||||
readonly query?: Readonly<Record<string, any>>;
|
||||
readonly data?: Readonly<Record<string, any>>;
|
||||
readonly params?: Readonly<Record<string, string>>;
|
||||
readonly user: Readonly<User>;
|
||||
}
|
||||
|
||||
@@ -30,7 +26,7 @@ export interface CoreContext {
|
||||
* Result object
|
||||
*/
|
||||
export interface CoreResult {
|
||||
json?: {};
|
||||
json?: Record<string, unknown>;
|
||||
text?: string;
|
||||
html?: string;
|
||||
}
|
||||
|
||||
@@ -163,7 +163,7 @@ export interface Var {
|
||||
/** Which mode is smb running in? active-directory | workgroup */
|
||||
shareSmbMode: string;
|
||||
shareUser: string;
|
||||
// shareUserExclude
|
||||
// ShareUserExclude
|
||||
shutdownTimeout: number;
|
||||
/** How long until emhttpd should spin down the data drives in your array. */
|
||||
spindownDelay: number;
|
||||
|
||||
@@ -15,7 +15,7 @@ interface Options {
|
||||
/**
|
||||
* Check if the username and password match a htpasswd file.
|
||||
*/
|
||||
export const checkAuth = async(options: Options): Promise<any> => {
|
||||
export const checkAuth = async (options: Options): Promise<any> => {
|
||||
const { username, password, file } = options;
|
||||
|
||||
// `valid` will be true if and only if
|
||||
|
||||
@@ -16,7 +16,7 @@ interface Options {
|
||||
/**
|
||||
* Check if the username and password match a htpasswd file
|
||||
*/
|
||||
export const ensureAuth = async(options: Options) => {
|
||||
export const ensureAuth = async (options: Options) => {
|
||||
const { username, password, file } = options;
|
||||
|
||||
// `valid` will be true if and only if
|
||||
|
||||
@@ -17,7 +17,7 @@ const dryRun = envs.DRY_RUN;
|
||||
/**
|
||||
* Run a command with emcmd.
|
||||
*/
|
||||
export const emcmd = async(commands: LooseObject) => {
|
||||
export const emcmd = async (commands: LooseObject) => {
|
||||
const url = `http://unix:${socketPath}:/update.htm`;
|
||||
const options = {
|
||||
qs: {
|
||||
|
||||
@@ -18,7 +18,7 @@ const getSubEndpoint = () => {
|
||||
return `http://localhost:${httpPort}/sub`;
|
||||
};
|
||||
|
||||
export const isNchanUp = async() => {
|
||||
export const isNchanUp = async () => {
|
||||
const isUp = await fetch(`${getSubEndpoint()}/non-existant`, {
|
||||
method: 'HEAD'
|
||||
})
|
||||
@@ -50,10 +50,10 @@ const endpointToStateMapping = {
|
||||
var: states.varState
|
||||
};
|
||||
|
||||
const subscribe = async(endpoint: string) => {
|
||||
await sleep(1000).then(async() => {
|
||||
const subscribe = async (endpoint: string) => {
|
||||
await sleep(1000).then(async () => {
|
||||
debugTimer(`subscribe(${endpoint})`);
|
||||
const response = await fetch(`${getSubEndpoint()}/${endpoint}`).catch(async() => {
|
||||
const response = await fetch(`${getSubEndpoint()}/${endpoint}`).catch(async () => {
|
||||
// If we throw then let's check if nchan is down
|
||||
// or if it's an actual error
|
||||
const isUp = await isNchanUp();
|
||||
@@ -109,7 +109,7 @@ const subscribe = async(endpoint: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
export const subscribeToNchanEndpoint = async(endpoint: string) => {
|
||||
export const subscribeToNchanEndpoint = async (endpoint: string) => {
|
||||
if (!Object.keys(endpointToStateMapping).includes(endpoint)) {
|
||||
throw new AppError(`Invalid nchan endpoint "${endpoint}".`);
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* Atomically sleep for a certain amount of milliseconds.
|
||||
* @param ms How many milliseconds to sleep for.
|
||||
*/
|
||||
export const atomicSleep = (ms: number): Promise<any> => {
|
||||
export const atomicSleep = async (ms: number): Promise<any> => {
|
||||
return new Promise(resolve => {
|
||||
// eslint-disable-next-line no-undef
|
||||
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { paths } from '../../paths';
|
||||
import { AppError } from '../../errors';
|
||||
|
||||
interface DockerError extends NodeJS.ErrnoException {
|
||||
address: string
|
||||
address: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,16 +17,16 @@ export const getEndpoints = (app: Express) => {
|
||||
|
||||
return deDeupedEndpoints.map(endpoint => {
|
||||
const flattenedMethods: string[] = flatten([
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
endpoint.methods,
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
endpoints.filter(({ path }) => path === endpoint.path).map(({ methods }) => methods)
|
||||
]);
|
||||
|
||||
const methods = flattenedMethods.filter((item, pos, self) => self.indexOf(item) === pos);
|
||||
|
||||
return {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
...endpoint,
|
||||
methods
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { FileMissingError } from '../../errors';
|
||||
|
||||
const cache = new CacheManager('unraid:utils:misc/get-machine-id');
|
||||
|
||||
export const getMachineId = async() => {
|
||||
export const getMachineId = async () => {
|
||||
const path = paths.get('machine-id');
|
||||
let machineId = cache.get('machine-id');
|
||||
|
||||
|
||||
@@ -1,159 +1,159 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import execa from 'execa';
|
||||
import { JSONSchemaForNPMPackageJsonFiles as PackageJson} from '@schemastore/package';
|
||||
import { JSONSchemaForNPMPackageJsonFiles as PackageJson } from '@schemastore/package';
|
||||
import { coreLogger } from '../../log';
|
||||
import { User } from '../../types';
|
||||
import { CacheManager } from '../../cache-manager';
|
||||
import { cleanStdout, ensurePermission } from '../../utils';
|
||||
|
||||
export interface NodeService {
|
||||
online?: boolean;
|
||||
uptime?: number;
|
||||
version?: string;
|
||||
online?: boolean;
|
||||
uptime?: number;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export const getNodeService = async (user: User, namespace: string): Promise<NodeService> => {
|
||||
// Check permissions
|
||||
ensurePermission(user, {
|
||||
resource: `service/${namespace}`,
|
||||
action: 'read',
|
||||
possession: 'any'
|
||||
});
|
||||
// Check permissions
|
||||
ensurePermission(user, {
|
||||
resource: `service/${namespace}`,
|
||||
action: 'read',
|
||||
possession: 'any'
|
||||
});
|
||||
|
||||
const cache = new CacheManager(`unraid:services/${namespace}`, Infinity);
|
||||
const cache = new CacheManager(`unraid:services/${namespace}`, Infinity);
|
||||
|
||||
const getCachedValue = (name: string) => {
|
||||
return cache.get<string>(name);
|
||||
};
|
||||
const getCachedValue = (name: string) => {
|
||||
return cache.get<string>(name);
|
||||
};
|
||||
|
||||
const setCachedValue = (name: string, value: string) => {
|
||||
cache.set(name, value);
|
||||
return value;
|
||||
};
|
||||
const setCachedValue = (name: string, value: string) => {
|
||||
cache.set(name, value);
|
||||
return value;
|
||||
};
|
||||
|
||||
const getUptime = async (pid: string | number) => {
|
||||
const uptime = await execa('ps', ['-p', String(pid), '-o', 'etimes', '--no-headers'])
|
||||
.then(cleanStdout)
|
||||
.catch(async error => {
|
||||
// No clue why this failed
|
||||
if (!error.stderr.includes('keyword not found')) {
|
||||
return '';
|
||||
}
|
||||
const getUptime = async (pid: string | number) => {
|
||||
const uptime = await execa('ps', ['-p', String(pid), '-o', 'etimes', '--no-headers'])
|
||||
.then(cleanStdout)
|
||||
.catch(async error => {
|
||||
// No clue why this failed
|
||||
if (!error.stderr.includes('keyword not found')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Retry with macos way
|
||||
// Borrowed from https://stackoverflow.com/a/28856613
|
||||
const command = `ps -p ${pid} -oetime= | tr '-' ':' | awk -F: '{ total=0; m=1; } { for (i=0; i < NF; i++) {total += $(NF-i)*m; m *= i >= 2 ? 24 : 60 }} {print total}'`;
|
||||
return execa.command(command, { shell: true }).then(cleanStdout).catch(() => '');
|
||||
});
|
||||
// Retry with macos way
|
||||
// Borrowed from https://stackoverflow.com/a/28856613
|
||||
const command = `ps -p ${pid} -oetime= | tr '-' ':' | awk -F: '{ total=0; m=1; } { for (i=0; i < NF; i++) {total += $(NF-i)*m; m *= i >= 2 ? 24 : 60 }} {print total}'`;
|
||||
return execa.command(command, { shell: true }).then(cleanStdout).catch(() => '');
|
||||
});
|
||||
|
||||
const parsedUptime = Number.parseInt(uptime, 10);
|
||||
return parsedUptime >= 0 ? parsedUptime : -1;
|
||||
};
|
||||
const parsedUptime = Number.parseInt(uptime, 10);
|
||||
return parsedUptime >= 0 ? parsedUptime : -1;
|
||||
};
|
||||
|
||||
const getPid = async (): Promise<string | void> => {
|
||||
let pid = getCachedValue('pid');
|
||||
const getPid = async (): Promise<string | void> => {
|
||||
let pid = getCachedValue('pid');
|
||||
|
||||
// Return cached pid
|
||||
if (pid) {
|
||||
return pid;
|
||||
}
|
||||
// Return cached pid
|
||||
if (pid) {
|
||||
return pid;
|
||||
}
|
||||
|
||||
coreLogger.debug('No PID found in cache for %s', namespace);
|
||||
pid = await execa.command(`pidof ${namespace}`)
|
||||
.then(output => {
|
||||
const pids = cleanStdout(output).split('\n');
|
||||
return pids[0];
|
||||
})
|
||||
.catch(async error => {
|
||||
// `pidof` is missing
|
||||
if (error.code === 'ENOENT') {
|
||||
// Fall back to using ps
|
||||
// ps axc returns a line like the following
|
||||
// 31424 s005 S+ 0:01.66 node
|
||||
// We match the last column and return the first
|
||||
return execa.command('ps axc').then(({stdout}) => {
|
||||
const foundProcess = stdout.split(/\n/).find(line => {
|
||||
const field = line.trim().split(/\s+/)[4];
|
||||
return field === namespace;
|
||||
})?.trim();
|
||||
return foundProcess ? foundProcess.split(/\s/)[0] : '';
|
||||
});
|
||||
}
|
||||
coreLogger.debug('No PID found in cache for %s', namespace);
|
||||
pid = await execa.command(`pidof ${namespace}`)
|
||||
.then(output => {
|
||||
const pids = cleanStdout(output).split('\n');
|
||||
return pids[0];
|
||||
})
|
||||
.catch(async error => {
|
||||
// `pidof` is missing
|
||||
if (error.code === 'ENOENT') {
|
||||
// Fall back to using ps
|
||||
// ps axc returns a line like the following
|
||||
// 31424 s005 S+ 0:01.66 node
|
||||
// We match the last column and return the first
|
||||
return execa.command('ps axc').then(({ stdout }) => {
|
||||
const foundProcess = stdout.split(/\n/).find(line => {
|
||||
const field = line.trim().split(/\s+/)[4];
|
||||
return field === namespace;
|
||||
})?.trim();
|
||||
return foundProcess ? foundProcess.split(/\s/)[0] : '';
|
||||
});
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
return '';
|
||||
});
|
||||
|
||||
// Process is offline
|
||||
if (!pid) {
|
||||
return;
|
||||
}
|
||||
// Process is offline
|
||||
if (!pid) {
|
||||
return;
|
||||
}
|
||||
|
||||
coreLogger.debug('Setting pid for %s to %s', namespace, pid);
|
||||
coreLogger.debug('Setting pid for %s to %s', namespace, pid);
|
||||
|
||||
// Update cache
|
||||
setCachedValue('pid', pid);
|
||||
// Update cache
|
||||
setCachedValue('pid', pid);
|
||||
|
||||
// Return new pid
|
||||
return pid;
|
||||
};
|
||||
// Return new pid
|
||||
return pid;
|
||||
};
|
||||
|
||||
const getPkgFilePath = async (pid: number | string) => {
|
||||
coreLogger.debug('Getting package.json path for %s', namespace);
|
||||
const getPkgFilePath = async (pid: number | string) => {
|
||||
coreLogger.debug('Getting package.json path for %s', namespace);
|
||||
|
||||
return execa.command(`pwdx ${pid}`).then(({ stdout }) => {
|
||||
return path.resolve(stdout.split(/\n/)[0].split(':')[1].trim());
|
||||
}).catch(async error =>{
|
||||
if (error.code === 'ENOENT') {
|
||||
return execa.command(`lsof -a -d cwd -p ${pid} -n -Fn`).then(({ stdout }) => stdout.split(/\n/)[2].substr(1));
|
||||
}
|
||||
return execa.command(`pwdx ${pid}`).then(({ stdout }) => {
|
||||
return path.resolve(stdout.split(/\n/)[0].split(':')[1].trim());
|
||||
}).catch(async error => {
|
||||
if (error.code === 'ENOENT') {
|
||||
return execa.command(`lsof -a -d cwd -p ${pid} -n -Fn`).then(({ stdout }) => stdout.split(/\n/)[2].substr(1));
|
||||
}
|
||||
|
||||
return '';
|
||||
});
|
||||
};
|
||||
return '';
|
||||
});
|
||||
};
|
||||
|
||||
const getVersion = async (pid: number | string) => {
|
||||
let cachedVersion = getCachedValue('version');
|
||||
const getVersion = async (pid: number | string) => {
|
||||
let cachedVersion = getCachedValue('version');
|
||||
|
||||
// Return cached version
|
||||
if (cachedVersion) {
|
||||
return cachedVersion;
|
||||
}
|
||||
// Return cached version
|
||||
if (cachedVersion) {
|
||||
return cachedVersion;
|
||||
}
|
||||
|
||||
// Update local vars
|
||||
const pkgFilePath = await getPkgFilePath(pid);
|
||||
const version = await fs.promises.readFile(`${pkgFilePath}/package.json`)
|
||||
.then(buffer => buffer.toString())
|
||||
.then(JSON.parse)
|
||||
.then((pkg: PackageJson) => pkg.version);
|
||||
// Update local vars
|
||||
const pkgFilePath = await getPkgFilePath(pid);
|
||||
const version = await fs.promises.readFile(`${pkgFilePath}/package.json`)
|
||||
.then(buffer => buffer.toString())
|
||||
.then(JSON.parse)
|
||||
.then((pkg: PackageJson) => pkg.version);
|
||||
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update cache
|
||||
setCachedValue('version', version);
|
||||
return version;
|
||||
};
|
||||
// Update cache
|
||||
setCachedValue('version', version);
|
||||
return version;
|
||||
};
|
||||
|
||||
const pid = await getPid();
|
||||
const pid = await getPid();
|
||||
|
||||
// If the pid doesn't exist it's offline
|
||||
if (!pid) {
|
||||
return {
|
||||
online: false
|
||||
};
|
||||
}
|
||||
// If the pid doesn't exist it's offline
|
||||
if (!pid) {
|
||||
return {
|
||||
online: false
|
||||
};
|
||||
}
|
||||
|
||||
const version = await getVersion(pid);
|
||||
const uptime = await getUptime(pid);
|
||||
const online = uptime >= 1;
|
||||
const version = await getVersion(pid);
|
||||
const uptime = await getUptime(pid);
|
||||
const online = uptime >= 1;
|
||||
|
||||
coreLogger.silly('%s version=%s uptime=%s online=%s', namespace, version, uptime, online);
|
||||
coreLogger.silly('%s version=%s uptime=%s online=%s', namespace, version, uptime, online);
|
||||
|
||||
return {
|
||||
online,
|
||||
uptime,
|
||||
version
|
||||
};
|
||||
return {
|
||||
online,
|
||||
uptime,
|
||||
version
|
||||
};
|
||||
};
|
||||
|
||||
@@ -16,7 +16,7 @@ export const loadState = <T>(filePath: string): T => {
|
||||
type: 'ini'
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
return camelCaseKeys(config, {
|
||||
deep: true
|
||||
});
|
||||
|
||||
@@ -32,7 +32,7 @@ const getPath = (filePath?: string) => {
|
||||
}
|
||||
|
||||
try {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
return paths.get(filePath) ?? filePath;
|
||||
} catch {
|
||||
return filePath;
|
||||
@@ -54,15 +54,13 @@ const getPath = (filePath?: string) => {
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
const fixObjectArrays = (object: {
|
||||
[key: string]: any;
|
||||
}) => {
|
||||
const fixObjectArrays = (object: Record<string, any>) => {
|
||||
// An object of arrays for keys that end in `:${number}`
|
||||
const temporaryArrays = {};
|
||||
|
||||
// An object without any array items
|
||||
const filteredObject = filterObject(object, (key, value) => {
|
||||
const [_, name, index] = [...((key as string).match(/(.*):(\d+$)/) ?? [])];
|
||||
const [_, name, index] = [...((key).match(/(.*):(\d+$)/) ?? [])];
|
||||
if (!name || !index) {
|
||||
return true;
|
||||
}
|
||||
@@ -100,7 +98,7 @@ export const parseConfig = <T>(options: Options): T => {
|
||||
}
|
||||
|
||||
// Parse file
|
||||
let data: { [key: string]: any };
|
||||
let data: Record<string, any>;
|
||||
if (filePath) {
|
||||
data = multiIniRead(filePath, {
|
||||
// eslint-disable-next-line camelcase
|
||||
@@ -131,7 +129,7 @@ export const parseConfig = <T>(options: Options): T => {
|
||||
);
|
||||
|
||||
// Convert all keys to camel case
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
return camelCaseKeys(result, {
|
||||
deep: true
|
||||
});
|
||||
|
||||
@@ -7,4 +7,4 @@
|
||||
* Sleep for a certain amount of milliseconds.
|
||||
* @param ms How many milliseconds to sleep for.
|
||||
*/
|
||||
export const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
export const sleep = async (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ApiKeyError } from '../../errors';
|
||||
|
||||
export const validateApiKeyFormat = (apiKey: string) => {
|
||||
const key = `${apiKey}`.trim();
|
||||
const length = key.length;
|
||||
const key = `${apiKey}`.trim();
|
||||
const length = key.length;
|
||||
|
||||
// Bail if key is missing
|
||||
if (length === 0) {
|
||||
@@ -25,5 +25,5 @@ export const validateApiKeyFormat = (apiKey: string) => {
|
||||
// For example "XXXXXXXXXXXXXXXXXXX"
|
||||
if (/^(.)\1+$/.test(key)) {
|
||||
throw new ApiKeyError(`Key is same char repeated, ${key}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -4,9 +4,9 @@ import { varState } from '../../states';
|
||||
import { AppError } from '../../errors';
|
||||
|
||||
export const validateApiKey = async (apiKey: string) => {
|
||||
const KEY_SERVER_KEY_VERIFICATION_ENDPOINT = process.env.KEY_SERVER_KEY_VERIFICATION_ENDPOINT ?? `https://keys.lime-technology.com/validate/apikey`;
|
||||
const KEY_SERVER_KEY_VERIFICATION_ENDPOINT = process.env.KEY_SERVER_KEY_VERIFICATION_ENDPOINT ?? 'https://keys.lime-technology.com/validate/apikey';
|
||||
|
||||
const sendFormToKeyServer = async (url: string, data: {}) => {
|
||||
const sendFormToKeyServer = async (url: string, data: Record<string, unknown>) => {
|
||||
if (!data) {
|
||||
throw new AppError('Missing data field.');
|
||||
}
|
||||
@@ -37,5 +37,5 @@ export const validateApiKey = async (apiKey: string) => {
|
||||
return false;
|
||||
}
|
||||
|
||||
return response.json().then(data => data.valid);
|
||||
return response.json().then(data => data.valid);
|
||||
};
|
||||
|
||||
@@ -9,8 +9,8 @@ import { ac } from '../../permissions';
|
||||
* Get permissions from an {@link https://onury.io/accesscontrol/?api=ac#AccessControl | AccessControl} role.
|
||||
* @param role The {@link https://onury.io/accesscontrol/?api=ac#AccessControl | AccessControl} role to be looked up.
|
||||
*/
|
||||
export const getPermissions = (role: string): object => {
|
||||
const grants: {} = ac.getGrants();
|
||||
export const getPermissions = (role: string): Record<string, unknown> => {
|
||||
const grants: Record<string, unknown> = ac.getGrants();
|
||||
const { $extend, ...roles } = grants[role];
|
||||
const inheritedRoles = Array.isArray($extend) ? $extend.map(role => getPermissions(role))[0] : {};
|
||||
return Object.assign({}, roles, inheritedRoles);
|
||||
|
||||
@@ -37,7 +37,7 @@ interface Options {
|
||||
/**
|
||||
* Load a PHP file.
|
||||
*/
|
||||
export const phpLoader = async(options: Options) => {
|
||||
export const phpLoader = async (options: Options) => {
|
||||
const { file, method = 'GET', query = {}, body = {} } = options;
|
||||
const options_ = [
|
||||
'./wrapper.php',
|
||||
|
||||
@@ -17,7 +17,7 @@ const guardTypeArg = (type: string) => {
|
||||
*
|
||||
* @param type handler's type
|
||||
*/
|
||||
const guardTypeHandler = (type: () => {}) => {
|
||||
const guardTypeHandler = (type: () => Record<string, unknown>) => {
|
||||
if (typeof type !== 'function') {
|
||||
throw new TypeError('Invalid argument: handler is expected to be a function');
|
||||
}
|
||||
@@ -29,9 +29,8 @@ const guardTypeHandler = (type: () => {}) => {
|
||||
* @class Upcast
|
||||
*/
|
||||
class Upcast {
|
||||
alias: {
|
||||
[key: string]: string
|
||||
};
|
||||
alias: Record<string, string>;
|
||||
|
||||
cast: any;
|
||||
|
||||
/**
|
||||
@@ -62,7 +61,7 @@ class Upcast {
|
||||
|
||||
// Default casters
|
||||
this.cast = {
|
||||
array: <T extends unknown>(value: T) => [value],
|
||||
array: <T>(value: T) => [value],
|
||||
boolean: (value: unknown) => Boolean(value),
|
||||
function: (value: unknown) => () => value,
|
||||
null: () => null,
|
||||
@@ -118,7 +117,7 @@ class Upcast {
|
||||
/**
|
||||
* Add custom cast
|
||||
*/
|
||||
add(type: string, handler: () => {}) {
|
||||
add(type: string, handler: () => Record<string, unknown>) {
|
||||
guardTypeArg(type);
|
||||
guardTypeHandler(handler);
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@ interface Device {
|
||||
* @param devices Devices to be checked.
|
||||
* @returns Processed devices.
|
||||
*/
|
||||
export const filterDevices = (devices: Device[]): Promise<Device[]> => {
|
||||
return asyncMap(devices, async(device: Device) => {
|
||||
export const filterDevices = async (devices: Device[]): Promise<Device[]> => {
|
||||
return asyncMap(devices, async (device: Device) => {
|
||||
// Don't run if we don't have the udevadm command available
|
||||
if (!commandExistsSync('udevadm')) {
|
||||
return device;
|
||||
|
||||
@@ -10,7 +10,7 @@ import { AppError } from '../../errors';
|
||||
let libvirt;
|
||||
let client;
|
||||
|
||||
export const getHypervisor = async() => {
|
||||
export const getHypervisor = async () => {
|
||||
// Return client if it's already connected
|
||||
if (client) {
|
||||
return client;
|
||||
|
||||
@@ -14,7 +14,7 @@ const regex = new RegExp(/^(?<id>\S+) "(?<type>[^"]+) \[(?<typeid>[a-f\d]{4})]"
|
||||
*
|
||||
* @returns Array of PCI devices
|
||||
*/
|
||||
export const getPciDevices = async(): Promise<PciDevice[]> => {
|
||||
export const getPciDevices = async (): Promise<PciDevice[]> => {
|
||||
const devices = await execa('lspci', ['-m', '-nn'])
|
||||
.catch(() => ({ stdout: '' }))
|
||||
.then(cleanStdout);
|
||||
|
||||
@@ -16,7 +16,7 @@ export type DomainLookupType = 'id' | 'uuid' | 'name';
|
||||
* @param id The domain's ID, UUID or name.
|
||||
* @private
|
||||
*/
|
||||
export const parseDomain = async(type: DomainLookupType, id: string): Promise<Domain> => {
|
||||
export const parseDomain = async (type: DomainLookupType, id: string): Promise<Domain> => {
|
||||
const types = {
|
||||
id: 'lookupDomainByIdAsync',
|
||||
uuid: 'lookupDomainByUUIDAsync',
|
||||
@@ -50,6 +50,6 @@ export const parseDomain = async(type: DomainLookupType, id: string): Promise<Do
|
||||
results.memoryStats = await domain.getMemoryStatsAsync();
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
return results;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,6 @@ import { Domain } from '../../types';
|
||||
/**
|
||||
* Parse domains.
|
||||
*/
|
||||
export const parseDomains = async(type: DomainLookupType, domains: string[]): Promise<Domain[]> => {
|
||||
return Promise.all(domains.map(domain => parseDomain(type, domain)));
|
||||
export const parseDomains = async (type: DomainLookupType, domains: string[]): Promise<Domain[]> => {
|
||||
return Promise.all(domains.map(async domain => parseDomain(type, domain)));
|
||||
};
|
||||
|
||||
@@ -4,12 +4,12 @@ import prettyBytes from 'pretty-bytes';
|
||||
import { coreLogger } from '../log';
|
||||
|
||||
const writeFile = (filePath: string, fileContents: string | Buffer) => {
|
||||
coreLogger.debug(`Writing ${prettyBytes(fileContents.length)} to ${filePath}`);
|
||||
fs.promises.writeFile(filePath, fileContents);
|
||||
}
|
||||
coreLogger.debug(`Writing ${prettyBytes(fileContents.length)} to ${filePath}`);
|
||||
fs.promises.writeFile(filePath, fileContents);
|
||||
};
|
||||
|
||||
export const writeToBoot = (filePath: string, fileContents: string | Buffer) => {
|
||||
const basePath = `/boot/config/plugins/dynamix/`;
|
||||
const resolvedPath = path.resolve(basePath, filePath);
|
||||
return writeFile(resolvedPath, fileContents);
|
||||
const basePath = '/boot/config/plugins/dynamix/';
|
||||
const resolvedPath = path.resolve(basePath, filePath);
|
||||
writeFile(resolvedPath, fileContents);
|
||||
};
|
||||
|
||||
@@ -15,7 +15,7 @@ export const plugins = () => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
const reloadPlugins = () => {
|
||||
coreLogger.debug(`Reloading plugins as it's been %s since last event.`, prettyMilliseconds(PLUGIN_RELOAD_TIME_MS));
|
||||
coreLogger.debug('Reloading plugins as it\'s been %s since last event.', prettyMilliseconds(PLUGIN_RELOAD_TIME_MS));
|
||||
|
||||
// Reload plugins
|
||||
// core.loaders.plugins();
|
||||
@@ -50,7 +50,7 @@ export const plugins = () => {
|
||||
watchers.push(watcher);
|
||||
},
|
||||
stop() {
|
||||
watchers.forEach(watcher => watcher.close());
|
||||
watchers.forEach(async watcher => watcher.close());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,19 +10,19 @@ import { coreLogger } from '../log';
|
||||
import { devicesState, networkState, nfsSecState, sharesState, slotsState, smbSecState, usersState, varState, ArrayState, State } from '../states';
|
||||
|
||||
const stateMapping = {
|
||||
'devs.ini': devicesState,
|
||||
'network.ini': networkState,
|
||||
'sec_nfs.ini': nfsSecState,
|
||||
'shares.ini': sharesState,
|
||||
'disks.ini': slotsState,
|
||||
'sec.ini': smbSecState,
|
||||
'users.ini': usersState,
|
||||
'var.ini': varState
|
||||
'devs.ini': devicesState,
|
||||
'network.ini': networkState,
|
||||
'sec_nfs.ini': nfsSecState,
|
||||
'shares.ini': sharesState,
|
||||
'disks.ini': slotsState,
|
||||
'sec.ini': smbSecState,
|
||||
'users.ini': usersState,
|
||||
'var.ini': varState
|
||||
};
|
||||
|
||||
const getState = (fullPath: string) => {
|
||||
const fileName = fullPath.split('/').pop()!;
|
||||
return Object.keys(stateMapping).includes(fileName) ? stateMapping[fileName] : undefined;
|
||||
const fileName = fullPath.split('/').pop()!;
|
||||
return Object.keys(stateMapping).includes(fileName) ? stateMapping[fileName] : undefined;
|
||||
};
|
||||
|
||||
export const states = () => {
|
||||
@@ -32,22 +32,22 @@ export const states = () => {
|
||||
let timeout: NodeJS.Timeout;
|
||||
|
||||
const reloadState = (state: ArrayState | State) => () => {
|
||||
coreLogger.debug(`Reloading state as it's been %s since last event.`, prettyMilliseconds(STATES_RELOAD_TIME_MS));
|
||||
coreLogger.debug('Reloading state as it\'s been %s since last event.', prettyMilliseconds(STATES_RELOAD_TIME_MS));
|
||||
|
||||
// Reload state
|
||||
try {
|
||||
state.reset();
|
||||
} catch (error) {
|
||||
coreLogger.error('failed resetting state', error);
|
||||
}
|
||||
// Reload state
|
||||
try {
|
||||
state.reset();
|
||||
} catch (error) {
|
||||
coreLogger.error('failed resetting state', error);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
start() {
|
||||
// Only run if NCHAN is disabled
|
||||
if (process.env.NCHAN !== 'disable') {
|
||||
return;
|
||||
}
|
||||
// Only run if NCHAN is disabled
|
||||
if (process.env.NCHAN !== 'disable') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update states when state files change
|
||||
const watcher = chokidar.watch(statesCwd, {
|
||||
@@ -56,20 +56,20 @@ export const states = () => {
|
||||
ignored: (path: string) => ['node_modules'].some(s => path.includes(s))
|
||||
});
|
||||
|
||||
coreLogger.debug('Loading watchers for %s', statesCwd);
|
||||
coreLogger.debug('Loading watchers for %s', statesCwd);
|
||||
|
||||
// State file has updated, updating state object
|
||||
watcher.on('all', (event, fullPath) => {
|
||||
// Reset timeout
|
||||
clearTimeout(timeout);
|
||||
clearTimeout(timeout);
|
||||
|
||||
// Ensure we only handle known files
|
||||
const state = getState(fullPath);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
// Ensure we only handle known files
|
||||
const state = getState(fullPath);
|
||||
if (!state) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update timeout
|
||||
// Update timeout
|
||||
timeout = setTimeout(reloadState(state), STATES_RELOAD_TIME_MS);
|
||||
coreLogger.debug('States directory %s has emitted %s event.', fullPath, event);
|
||||
});
|
||||
@@ -78,7 +78,7 @@ export const states = () => {
|
||||
watchers.push(watcher);
|
||||
},
|
||||
stop() {
|
||||
watchers.forEach(watcher => watcher.close());
|
||||
watchers.forEach(async watcher => watcher.close());
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -161,12 +161,12 @@ const getPluginModule = (pluginName: string, pluginModuleName: string) => {
|
||||
* including the field name, path to the field from the root, and more.
|
||||
*/
|
||||
class FuncDirective extends SchemaDirectiveVisitor {
|
||||
visitFieldDefinition(field: { [key: string]: any }) {
|
||||
// @ts-ignore
|
||||
visitFieldDefinition(field: Record<string, any>) {
|
||||
// @ts-expect-error
|
||||
const { args } = this;
|
||||
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;
|
||||
field.resolve = async function (_source, directiveArgs: Record<string, any>, { user }, info: Record<string, any>) {
|
||||
const { module: moduleName, result: resultType } = args;
|
||||
const { plugin: pluginName, module: pluginModuleName, result: pluginType, input, ...params } = directiveArgs;
|
||||
const operationType = info.operation.operation;
|
||||
const query = {
|
||||
...directiveArgs.query,
|
||||
@@ -189,7 +189,7 @@ class FuncDirective extends SchemaDirectiveVisitor {
|
||||
let func;
|
||||
try {
|
||||
if (pluginName) {
|
||||
// @ts-ignore
|
||||
// @ts-expect-error
|
||||
const { filePath } = getPluginModule(pluginName, pluginModuleName);
|
||||
const pluginModule = require(filePath);
|
||||
// The file will either use a default export or a named one
|
||||
@@ -266,10 +266,8 @@ const ensureApiKey = (apiKeyToCheck: string) => {
|
||||
if (!apiManager.isValid(apiKeyToCheck)) {
|
||||
throw new AppError('Invalid API key.');
|
||||
}
|
||||
} else {
|
||||
if (process.env.NODE_ENV !== 'development') {
|
||||
throw new AppError('No valid API keys active.');
|
||||
}
|
||||
} else if (process.env.NODE_ENV !== 'development') {
|
||||
throw new AppError('No valid API keys active.');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -309,7 +307,7 @@ bus.on('slots', async () => {
|
||||
});
|
||||
|
||||
// Update info/hostname when hostname changes
|
||||
bus.on('varstate', async (data) => {
|
||||
bus.on('varstate', async data => {
|
||||
const hostname = data.varstate.node.name;
|
||||
// @todo: Create a system user for this
|
||||
const user = usersState.findOne({ name: 'root' });
|
||||
@@ -370,13 +368,13 @@ setIntervalAsync(async () => {
|
||||
export const graphql = {
|
||||
introspection: debug,
|
||||
playground: debug ? {
|
||||
subscriptionEndpoint: '/graphql',
|
||||
subscriptionEndpoint: '/graphql'
|
||||
} : false,
|
||||
schema,
|
||||
types,
|
||||
resolvers,
|
||||
subscriptions: {
|
||||
onConnect: async (connectionParams: { [key: string]: string }) => new Promise((resolve, reject) => {
|
||||
onConnect: async (connectionParams: Record<string, string>) => new Promise((resolve, reject) => {
|
||||
try {
|
||||
const apiKey = connectionParams['x-api-key'];
|
||||
const user = apiKeyToUser(apiKey);
|
||||
@@ -387,10 +385,10 @@ export const graphql = {
|
||||
// Update ws connection count and other needed values
|
||||
wsHasConnected(websocketId);
|
||||
|
||||
return resolve({
|
||||
resolve({
|
||||
user,
|
||||
websocketId
|
||||
});
|
||||
}); return;
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ export const Long = GraphQLLong;
|
||||
export const UUID = GraphQLUUID;
|
||||
|
||||
export {
|
||||
Query,
|
||||
Subscription,
|
||||
UserAccount
|
||||
};
|
||||
Query,
|
||||
Subscription,
|
||||
UserAccount
|
||||
};
|
||||
|
||||
@@ -73,77 +73,77 @@ const states = {
|
||||
};
|
||||
|
||||
export default async () => {
|
||||
const dynamixBasePath = paths.get('dynamix-base')!;
|
||||
const configFilePath = join(dynamixBasePath, 'case-model.cfg');
|
||||
const customImageFilePath = join(dynamixBasePath, 'case-model.png');
|
||||
const dynamixBasePath = paths.get('dynamix-base')!;
|
||||
const configFilePath = join(dynamixBasePath, 'case-model.cfg');
|
||||
const customImageFilePath = join(dynamixBasePath, 'case-model.png');
|
||||
|
||||
// If the config file doesn't exist then it's a new OS install
|
||||
// Default to "default"
|
||||
if (!existsSync(configFilePath)) {
|
||||
return { case: states.default };
|
||||
}
|
||||
// If the config file doesn't exist then it's a new OS install
|
||||
// Default to "default"
|
||||
if (!existsSync(configFilePath)) {
|
||||
return { case: states.default };
|
||||
}
|
||||
|
||||
// Attempt to get case from file
|
||||
const serverCase = await fs.readFile(configFilePath)
|
||||
.then(buffer => buffer.toString().split('\n')[0])
|
||||
.catch(() => 'error_reading_config_file');
|
||||
// Attempt to get case from file
|
||||
const serverCase = await fs.readFile(configFilePath)
|
||||
.then(buffer => buffer.toString().split('\n')[0])
|
||||
.catch(() => 'error_reading_config_file');
|
||||
|
||||
// Config file can't be read, maybe a permissions issue?
|
||||
if (serverCase === 'error_reading_config_file') {
|
||||
return { case: states.couldNotReadConfigFile };
|
||||
}
|
||||
// Config file can't be read, maybe a permissions issue?
|
||||
if (serverCase === 'error_reading_config_file') {
|
||||
return { case: states.couldNotReadConfigFile };
|
||||
}
|
||||
|
||||
// Custom icon
|
||||
if (serverCase.includes('.')) {
|
||||
// Ensure image exists
|
||||
if (!existsSync(customImageFilePath)) {
|
||||
return { case: states.imageMissing };
|
||||
}
|
||||
// Custom icon
|
||||
if (serverCase.includes('.')) {
|
||||
// Ensure image exists
|
||||
if (!existsSync(customImageFilePath)) {
|
||||
return { case: states.imageMissing };
|
||||
}
|
||||
|
||||
// Ensure we're within size limits
|
||||
if (isOverFileSizeLimit(customImageFilePath)) {
|
||||
graphqlLogger.debug('"custom-case.png" is too big.');
|
||||
return { case: states.imageTooBig };
|
||||
}
|
||||
// Ensure we're within size limits
|
||||
if (isOverFileSizeLimit(customImageFilePath)) {
|
||||
graphqlLogger.debug('"custom-case.png" is too big.');
|
||||
return { case: states.imageTooBig };
|
||||
}
|
||||
|
||||
try {
|
||||
// Get image buffer
|
||||
const fileBuffer = await fs.readFile(customImageFilePath);
|
||||
try {
|
||||
// Get image buffer
|
||||
const fileBuffer = await fs.readFile(customImageFilePath);
|
||||
|
||||
// Likely not an actual image
|
||||
// 73 bytes is close to the smallest we can get https://garethrees.org/2007/11/14/pngcrush/
|
||||
if (fileBuffer.length <= 25) {
|
||||
return {
|
||||
case: states.couldNotReadImage
|
||||
}
|
||||
}
|
||||
// Likely not an actual image
|
||||
// 73 bytes is close to the smallest we can get https://garethrees.org/2007/11/14/pngcrush/
|
||||
if (fileBuffer.length <= 25) {
|
||||
return {
|
||||
case: states.couldNotReadImage
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
case: {
|
||||
...states.custom,
|
||||
base64: fileBuffer.toString('base64'),
|
||||
url: serverCase
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
case: states.couldNotReadImage
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
case: {
|
||||
...states.custom,
|
||||
base64: fileBuffer.toString('base64'),
|
||||
url: serverCase
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
case: states.couldNotReadImage
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Blank cfg file?
|
||||
if (serverCase.trim().length === 0) {
|
||||
return {
|
||||
case: states.default
|
||||
};
|
||||
}
|
||||
// Blank cfg file?
|
||||
if (serverCase.trim().length === 0) {
|
||||
return {
|
||||
case: states.default
|
||||
};
|
||||
}
|
||||
|
||||
// Non-custom icon
|
||||
return {
|
||||
case: {
|
||||
...states.default,
|
||||
icon: serverCase
|
||||
}
|
||||
};
|
||||
};
|
||||
// Non-custom icon
|
||||
return {
|
||||
case: {
|
||||
...states.default,
|
||||
icon: serverCase
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,10 +10,10 @@ import servers from './servers';
|
||||
import vms from './vms';
|
||||
|
||||
export const Query = {
|
||||
display,
|
||||
info,
|
||||
online,
|
||||
vms,
|
||||
server,
|
||||
servers
|
||||
display,
|
||||
info,
|
||||
online,
|
||||
vms,
|
||||
server,
|
||||
servers
|
||||
};
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user