You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wiki/backend/api/system.js

301 lines
7.8 KiB

import path from 'node:path'
import os from 'node:os'
import { DateTime } from 'luxon'
import { filesize } from 'filesize'
import { isNil } from 'es-toolkit/predicate'
import { gte, sql } from 'drizzle-orm'
import {
groups as groupsTable,
pages as pagesTable,
tags as tagsTable,
users as usersTable
} from '../db/schema.js'
/**
* System API Routes
*/
async function routes(app) {
/**
* SYSTEM INFO
*/
app.get(
'/info',
{
config: {
permissions: ['read:dashboard']
},
schema: {
summary: 'System Info',
tags: ['System'],
response: {
200: {
description: 'System Info',
type: 'object',
properties: {
configFile: {
type: 'string'
},
cpuCores: {
type: 'number'
},
currentVersion: {
type: 'string'
},
dbHost: {
type: 'string'
},
groupsTotal: {
type: 'number'
},
hostname: {
type: 'string'
},
httpPort: {
type: 'number'
},
isMailConfigured: {
type: 'boolean'
},
isSchedulerHealthy: {
type: 'boolean'
},
latestVersion: {
type: 'string'
},
latestVersionReleaseDate: {
type: 'string',
format: 'date-time'
},
loginsPastDay: {
type: 'number'
},
nodeVersion: {
type: 'string'
},
operatingSystem: {
type: 'string'
},
pagesTotal: {
type: 'number'
},
platform: {
type: 'string'
},
ramTotal: {
type: 'string'
},
tagsTotal: {
type: 'string'
},
upgradeCapable: {
type: 'boolean'
},
usersTotal: {
type: 'number'
},
workingDirectory: {
type: 'string'
}
}
}
}
}
},
async () => {
return {
configFile: path.join(process.cwd(), 'config.yml'),
cpuCores: os.cpus().length,
currentVersion: WIKI.version,
dbHost: WIKI.config.db.host,
dbVersion: WIKI.dbManager.VERSION,
groupsTotal: await WIKI.db.$count(groupsTable),
hostname: os.hostname(),
httpPort: 0,
isMailConfigured: WIKI.config?.mail?.host?.length > 2,
isSchedulerHealthy: true, // TODO:
latestVersion: WIKI.config.update.version,
latestVersionReleaseDate: WIKI.config.update.versionDate,
loginsPastDay: await WIKI.db.$count(
usersTable,
gte(usersTable.lastLoginAt, sql`NOW() - INTERVAL '1 DAY'`)
),
nodeVersion: process.version.substring(1),
operatingSystem: `${os.type()} (${os.platform()}) ${os.release()} ${os.arch()}`,
pagesTotal: await WIKI.db.$count(pagesTable),
platform: os.platform(),
ramTotal: filesize(os.totalmem()),
tagsTotal: await WIKI.db.$count(tagsTable),
upgradeCapable: !isNil(process.env.UPGRADE_COMPANION),
usersTotal: await WIKI.db.$count(usersTable),
workingDirectory: process.cwd()
}
}
)
/**
* SYSTEM FLAGS
*/
app.get(
'/flags',
{
schema: {
summary: 'System Flags',
tags: ['System'],
response: {
200: {
description: 'System Flags',
type: 'object',
properties: {
experimental: {
type: 'boolean'
},
authDebug: {
type: 'boolean'
},
sqlLog: {
type: 'boolean'
}
}
}
}
}
},
async () => {
return WIKI.config.flags
}
)
/**
* LIST SYSTEM INSTANCES
*/
app.get(
'/instances',
{
config: {
permissions: ['manage:system']
},
schema: {
summary: 'List System Instances',
tags: ['System'],
response: {
200: {
description: 'List of all system instances',
type: 'array',
items: {
type: 'object',
properties: {
id: {
type: 'string'
},
activeConnections: {
type: 'number'
},
activeListeners: {
type: 'number'
},
dbUser: {
type: 'string'
},
dbFirstSeen: {
type: 'string',
format: 'date-time'
},
dbLastSeen: {
type: 'string',
format: 'date-time'
},
ip: {
type: 'string'
}
}
}
}
}
}
},
async () => {
const instRaw = await WIKI.db.execute(
sql`SELECT usename, client_addr, application_name, backend_start, state_change FROM pg_stat_activity WHERE datname = ${WIKI.dbManager.dbName} AND application_name LIKE 'Wiki.js%'`
)
const insts = {}
for (const inst of instRaw.rows) {
const instId = inst.application_name.substring(10, 20)
const conType = [':MAIN', ':WORKER'].some((ct) => inst.application_name.endsWith(ct))
? 'main'
: 'sub'
inst.backend_start = DateTime.fromSQL(inst.backend_start).toISO()
inst.state_change = DateTime.fromSQL(inst.state_change).toISO()
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 Object.values(insts)
}
)
/**
* CHECK FOR UPDATE
*/
app.post(
'/checkForUpdate',
{
config: {
permissions: ['read:dashboard']
},
schema: {
summary: 'Check for Updates',
tags: ['System'],
response: {
200: {
description: 'Update Info',
type: 'object',
properties: {
current: {
type: 'string'
},
latest: {
type: 'string'
},
latestDate: {
type: 'string',
format: 'date-time'
}
}
}
}
}
},
async () => {
const renderJob = await WIKI.scheduler.addJob({
task: 'checkVersion',
maxRetries: 0,
promise: true
})
await renderJob.promise
return {
current: WIKI.version,
latest: WIKI.config.update.version,
latestDate: WIKI.config.update.versionDate
}
}
)
}
export default routes