const _ = require ( 'lodash' )
const util = require ( 'node:util' )
const getos = util . promisify ( require ( 'getos' ) )
const os = require ( 'node:os' )
const filesize = require ( 'filesize' )
const path = require ( 'path' )
const fs = require ( 'fs-extra' )
const { DateTime } = require ( 'luxon' )
const graphHelper = require ( '../../helpers/graph' )
module . exports = {
Query : {
systemFlags ( ) {
return _ . transform ( WIKI . config . flags , ( result , value , key ) => {
result . push ( { key , value } )
} , [ ] )
} ,
async systemInfo ( ) { return { } } ,
async systemExtensions ( ) {
const exts = Object . values ( WIKI . extensions . ext ) . map ( ext => _ . pick ( ext , [ 'key' , 'title' , 'description' , 'isInstalled' , 'isInstallable' ] ) )
for ( const ext of exts ) {
ext . isCompatible = await WIKI . extensions . ext [ ext . key ] . isCompatible ( )
}
return exts
} ,
async systemInstances ( ) {
const instRaw = await WIKI . db . knex ( 'pg_stat_activity' )
. select ( [
'usename' ,
'client_addr' ,
'application_name' ,
'backend_start' ,
'state_change'
] )
. where ( 'datname' , WIKI . db . knex . client . connectionSettings . database )
. andWhereLike ( 'application_name' , 'Wiki.js%' )
const insts = { }
for ( const inst of instRaw ) {
const instId = inst . application _name . substring ( 10 , 20 )
const conType = inst . application _name . includes ( ':MAIN' ) ? 'main' : 'sub'
const curInst = insts [ instId ] ? ? {
activeConnections : 0 ,
activeListeners : 0 ,
dbFirstSeen : inst . backend _start ,
dbLastSeen : inst . state _change
}
insts [ instId ] = {
id : instId ,
activeConnections : conType === 'main' ? curInst . activeConnections + 1 : curInst . activeConnections ,
activeListeners : conType === 'sub' ? curInst . activeListeners + 1 : curInst . activeListeners ,
dbUser : inst . usename ,
dbFirstSeen : curInst . dbFirstSeen > inst . backend _start ? inst . backend _start : curInst . dbFirstSeen ,
dbLastSeen : curInst . dbLastSeen < inst . state _change ? inst . state _change : curInst . dbLastSeen ,
ip : inst . client _addr
}
}
return _ . values ( insts )
} ,
systemSecurity ( ) {
return WIKI . config . security
} ,
async systemJobs ( obj , args ) {
const results = args . states ? . length > 0 ?
await WIKI . db . knex ( 'jobHistory' ) . whereIn ( 'state' , args . states . map ( s => s . toLowerCase ( ) ) ) . orderBy ( 'startedAt' , 'desc' ) :
await WIKI . db . knex ( 'jobHistory' ) . orderBy ( 'startedAt' , 'desc' )
return results . map ( r => ( {
... r ,
state : r . state . toUpperCase ( )
} ) )
} ,
async systemJobsScheduled ( obj , args ) {
return WIKI . db . knex ( 'jobSchedule' ) . orderBy ( 'task' )
} ,
async systemJobsUpcoming ( obj , args ) {
return WIKI . db . knex ( 'jobs' ) . orderBy ( [
{ column : 'waitUntil' , order : 'asc' , nulls : 'first' } ,
{ column : 'createdAt' , order : 'asc' }
] )
}
} ,
Mutation : {
async cancelJob ( obj , args , context ) {
WIKI . logger . info ( ` Admin requested cancelling job ${ args . id } ... ` )
try {
const result = await WIKI . db . knex ( 'jobs' )
. where ( 'id' , args . id )
. del ( )
if ( result === 1 ) {
WIKI . logger . info ( ` Cancelled job ${ args . id } [ OK ] ` )
} else {
throw new Error ( 'Job has already entered active state or does not exist.' )
}
return {
operation : graphHelper . generateSuccess ( 'Cancelled job successfully.' )
}
} catch ( err ) {
WIKI . logger . warn ( err )
return graphHelper . generateError ( err )
}
} ,
async disconnectWS ( obj , args , context ) {
WIKI . servers . ws . disconnectSockets ( true )
WIKI . logger . info ( 'All active websocket connections have been terminated.' )
return {
operation : graphHelper . generateSuccess ( 'All websocket connections closed successfully.' )
}
} ,
async installExtension ( obj , args , context ) {
try {
await WIKI . extensions . ext [ args . key ] . install ( )
// TODO: broadcast ext install
return {
operation : graphHelper . generateSuccess ( 'Extension installed successfully' )
}
} catch ( err ) {
return graphHelper . generateError ( err )
}
} ,
async retryJob ( obj , args , context ) {
WIKI . logger . info ( ` Admin requested rescheduling of job ${ args . id } ... ` )
try {
const job = await WIKI . db . knex ( 'jobHistory' )
. where ( 'id' , args . id )
. first ( )
if ( ! job ) {
throw new Error ( 'No such job found.' )
} else if ( job . state === 'interrupted' ) {
throw new Error ( 'Cannot reschedule a task that has been interrupted. It will automatically be retried shortly.' )
} else if ( job . state === 'failed' && job . attempt < job . maxRetries ) {
throw new Error ( 'Cannot reschedule a task that has not reached its maximum retry attempts.' )
}
await WIKI . db . knex ( 'jobs' )
. insert ( {
id : job . id ,
task : job . task ,
useWorker : job . useWorker ,
payload : job . payload ,
retries : job . attempt ,
maxRetries : job . maxRetries ,
isScheduled : job . wasScheduled ,
createdBy : WIKI . INSTANCE _ID
} )
WIKI . logger . info ( ` Job ${ args . id } has been rescheduled [ OK ] ` )
return {
operation : graphHelper . generateSuccess ( 'Job rescheduled successfully.' )
}
} catch ( err ) {
WIKI . logger . warn ( err )
return graphHelper . generateError ( err )
}
} ,
async updateSystemFlags ( obj , args , context ) {
WIKI . config . flags = _ . transform ( args . flags , ( result , row ) => {
_ . set ( result , row . key , row . value )
} , { } )
await WIKI . configSvc . applyFlags ( )
await WIKI . configSvc . saveToDb ( [ 'flags' ] )
return {
operation : graphHelper . generateSuccess ( 'System Flags applied successfully' )
}
} ,
async updateSystemSecurity ( obj , args , context ) {
WIKI . config . security = _ . defaultsDeep ( _ . omit ( args , [ '__typename' ] ) , WIKI . config . security )
// TODO: broadcast config update
await WIKI . configSvc . saveToDb ( [ 'security' ] )
return {
status : graphHelper . generateSuccess ( 'System Security configuration applied successfully' )
}
}
} ,
SystemInfo : {
configFile ( ) {
return path . join ( process . cwd ( ) , 'config.yml' )
} ,
cpuCores ( ) {
return os . cpus ( ) . length
} ,
currentVersion ( ) {
return WIKI . version
} ,
dbHost ( ) {
return WIKI . config . db . host
} ,
dbVersion ( ) {
return _ . get ( WIKI . db , 'knex.client.version' , 'Unknown Version' )
} ,
hostname ( ) {
return os . hostname ( )
} ,
httpPort ( ) {
return WIKI . servers . servers . http ? _ . get ( WIKI . servers . servers . http . address ( ) , 'port' , 0 ) : 0
} ,
httpRedirection ( ) {
return _ . get ( WIKI . config , 'server.sslRedir' , false )
} ,
httpsPort ( ) {
return WIKI . servers . servers . https ? _ . get ( WIKI . servers . servers . https . address ( ) , 'port' , 0 ) : 0
} ,
isMailConfigured ( ) {
return WIKI . config ? . mail ? . host ? . length > 2
} ,
async isSchedulerHealthy ( ) {
const results = await WIKI . db . knex ( 'jobHistory' ) . count ( '* as total' ) . whereIn ( 'state' , [ 'failed' , 'interrupted' ] ) . andWhere ( 'startedAt' , '>=' , DateTime . utc ( ) . minus ( { days : 1 } ) . toISO ( ) ) . first ( )
return _ . toSafeInteger ( results ? . total ) === 0
} ,
latestVersion ( ) {
return WIKI . system . updates . version
} ,
latestVersionReleaseDate ( ) {
return DateTime . fromISO ( WIKI . system . updates . releaseDate ) . toJSDate ( )
} ,
nodeVersion ( ) {
return process . version . substr ( 1 )
} ,
async operatingSystem ( ) {
let osLabel = ` ${ os . type ( ) } ( ${ os . platform ( ) } ) ${ os . release ( ) } ${ os . arch ( ) } `
if ( os . platform ( ) === 'linux' ) {
const osInfo = await getos ( )
osLabel = ` ${ os . type ( ) } - ${ osInfo . dist } ( ${ osInfo . codename || os . platform ( ) } ) ${ osInfo . release || os . release ( ) } ${ os . arch ( ) } `
}
return osLabel
} ,
async platform ( ) {
const isDockerized = await fs . pathExists ( '/.dockerenv' )
if ( isDockerized ) {
return 'docker'
}
return os . platform ( )
} ,
ramTotal ( ) {
return filesize ( os . totalmem ( ) )
} ,
sslDomain ( ) {
return WIKI . config . ssl . enabled && WIKI . config . ssl . provider === 'letsencrypt' ? WIKI . config . ssl . domain : null
} ,
sslExpirationDate ( ) {
return WIKI . config . ssl . enabled && WIKI . config . ssl . provider === 'letsencrypt' ? _ . get ( WIKI . config . letsencrypt , 'payload.expires' , null ) : null
} ,
sslProvider ( ) {
return WIKI . config . ssl . enabled ? WIKI . config . ssl . provider : null
} ,
sslStatus ( ) {
return 'OK'
} ,
sslSubscriberEmail ( ) {
return WIKI . config . ssl . enabled && WIKI . config . ssl . provider === 'letsencrypt' ? WIKI . config . ssl . subscriberEmail : null
} ,
async upgradeCapable ( ) {
return ! _ . isNil ( process . env . UPGRADE _COMPANION )
} ,
workingDirectory ( ) {
return process . cwd ( )
} ,
async groupsTotal ( ) {
const total = await WIKI . db . groups . query ( ) . count ( '* as total' ) . first ( )
return _ . toSafeInteger ( total . total )
} ,
async pagesTotal ( ) {
const total = await WIKI . db . pages . query ( ) . count ( '* as total' ) . first ( )
return _ . toSafeInteger ( total . total )
} ,
async usersTotal ( ) {
const total = await WIKI . db . users . query ( ) . count ( '* as total' ) . first ( )
return _ . toSafeInteger ( total . total )
} ,
async tagsTotal ( ) {
const total = await WIKI . db . tags . query ( ) . count ( '* as total' ) . first ( )
return _ . toSafeInteger ( total . total )
}
}
}