chore: lint

This commit is contained in:
Alexis Tyler
2021-01-28 15:45:14 +10:30
parent e2fca3d588
commit 4e1b0bd72c
121 changed files with 1012 additions and 1012 deletions

7
.eslintrc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
extends: [
'@unraid'
],
};

2
app/cache/index.ts vendored
View File

@@ -1 +1 @@
export * from './user';
export * from './user';

32
app/cache/user.ts vendored
View File

@@ -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[];
}

View File

@@ -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.

View File

@@ -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) {

View File

@@ -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();
});

View File

@@ -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
};

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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' });

View File

@@ -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',

View File

@@ -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');

View File

@@ -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',

View File

@@ -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

View File

@@ -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

View File

@@ -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();
};

View File

@@ -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 {

View File

@@ -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);

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -12,9 +12,9 @@ type State = 'start' | 'cancel' | 'resume' | 'cancel';
interface Context extends CoreContext {
data: {
state?: State
correct?: boolean
}
state?: State;
correct?: boolean;
};
}
/**

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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',

View File

@@ -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 {

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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];
})

View File

@@ -16,7 +16,7 @@ interface Context extends CoreContext {
}
interface Result extends CoreResult {
json: Plugin[]
json: Plugin[];
}
export const getPlugins = (context: Readonly<Context>): Result => {

View File

@@ -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 [];

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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);

View File

@@ -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

View File

@@ -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)}`,

View File

@@ -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

View File

@@ -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;

View File

@@ -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

View File

@@ -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',

View File

@@ -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',

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -10,7 +10,7 @@ export type NotifierLevel = 'info' | 'error' | 'debug';
export interface NotifierOptions {
level: NotifierLevel;
helpers?: object;
helpers?: Record<string, unknown>;
template?: string;
}

View File

@@ -9,7 +9,7 @@ import { NotifierSendOptions } from './notifier';
type Transport = 'email' | 'push' | 'ios' | 'android';
interface Options extends HttpNotifierOptions {
transport?: Transport
transport?: Transport;
}
/**

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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 => {

View File

@@ -92,7 +92,7 @@ class SmbSec extends ArrayState {
return data;
}
find(query?: {}): any[] {
find(query?: Record<string, unknown>): any[] {
return super.find(query);
}
}

View File

@@ -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, {

View File

@@ -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;
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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

View File

@@ -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: {

View File

@@ -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}".`);
}

View File

@@ -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);

View File

@@ -7,7 +7,7 @@ import { paths } from '../../paths';
import { AppError } from '../../errors';
interface DockerError extends NodeJS.ErrnoException {
address: string
address: string;
}
/**

View File

@@ -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
};

View File

@@ -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');

View File

@@ -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
};
};

View File

@@ -16,7 +16,7 @@ export const loadState = <T>(filePath: string): T => {
type: 'ini'
});
// @ts-ignore
// @ts-expect-error
return camelCaseKeys(config, {
deep: true
});

View File

@@ -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
});

View File

@@ -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));

View File

@@ -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}`);
}
}
}
};

View File

@@ -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);
};

View File

@@ -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);

View File

@@ -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',

View File

@@ -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);

View File

@@ -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;

View File

@@ -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;

View File

@@ -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);

View File

@@ -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;
};

View File

@@ -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)));
};

View File

@@ -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);
};

View File

@@ -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());
}
};
};

View File

@@ -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());
}
};
};

View File

@@ -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);
}

View File

@@ -15,7 +15,7 @@ export const Long = GraphQLLong;
export const UUID = GraphQLUUID;
export {
Query,
Subscription,
UserAccount
};
Query,
Subscription,
UserAccount
};

View File

@@ -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
}
};
};

View File

@@ -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