226 lines
5.3 KiB
JavaScript
226 lines
5.3 KiB
JavaScript
const si = require('systeminformation');
|
|
|
|
class StatsGenerator {
|
|
// symbols would be better because they're unique, but harder to serialize them
|
|
static Type = Object.freeze({
|
|
// should contain key value: number
|
|
TIME: 'time',
|
|
// should contain key value: number
|
|
BYTE: 'byte',
|
|
// should contain key value: { used: number, total: number }
|
|
BYTE_USAGE: 'byteUsage',
|
|
// should contain key data: Array<{ key: string, value: number | string }>
|
|
// and optionally a count/total
|
|
DETAILED: 'detailed',
|
|
// hidden type should be skipped during iteration, can contain anything
|
|
// these should be treated on a case by case basis on the frontend
|
|
HIDDEN: 'hidden'
|
|
});
|
|
|
|
static statGenerators = {
|
|
system: StatsGenerator.getSystemInfo,
|
|
fileSystems: StatsGenerator.getFileSystemsInfo,
|
|
uploads: StatsGenerator.getUploadsInfo,
|
|
users: StatsGenerator.getUsersInfo,
|
|
albums: StatsGenerator.getAlbumStats
|
|
};
|
|
|
|
static keyOrder = Object.keys(StatsGenerator.statGenerators);
|
|
|
|
static async getSystemInfo() {
|
|
const os = await si.osInfo();
|
|
|
|
const currentLoad = await si.currentLoad();
|
|
const mem = await si.mem();
|
|
const time = si.time();
|
|
const nodeUptime = process.uptime();
|
|
|
|
return {
|
|
'Platform': `${os.platform} ${os.arch}`,
|
|
'Distro': `${os.distro} ${os.release}`,
|
|
'Kernel': os.kernel,
|
|
'CPU Load': `${currentLoad.currentload.toFixed(1)}%`,
|
|
'CPUs Load': currentLoad.cpus.map(cpu => `${cpu.load.toFixed(1)}%`).join(', '),
|
|
'System Memory': {
|
|
value: {
|
|
used: mem.active,
|
|
total: mem.total
|
|
},
|
|
type: StatsGenerator.Type.BYTE_USAGE
|
|
},
|
|
'Memory Usage': {
|
|
value: process.memoryUsage().rss,
|
|
type: StatsGenerator.Type.BYTE
|
|
},
|
|
'System Uptime': {
|
|
value: time.uptime,
|
|
type: StatsGenerator.Type.TIME
|
|
},
|
|
'Node.js': `${process.versions.node}`,
|
|
'Service Uptime': {
|
|
value: Math.floor(nodeUptime),
|
|
type: StatsGenerator.Type.TIME
|
|
}
|
|
};
|
|
}
|
|
|
|
static async getFileSystemsInfo() {
|
|
const stats = {};
|
|
|
|
const fsSize = await si.fsSize();
|
|
for (const fs of fsSize) {
|
|
stats[`${fs.fs} (${fs.type}) on ${fs.mount}`] = {
|
|
value: {
|
|
total: fs.size,
|
|
used: fs.used
|
|
},
|
|
type: StatsGenerator.Type.BYTE_USAGE
|
|
};
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
static async getUploadsInfo(db) {
|
|
const stats = {
|
|
'Total': 0,
|
|
'Images': 0,
|
|
'Videos': 0,
|
|
'Others': {
|
|
data: {},
|
|
count: 0,
|
|
type: StatsGenerator.Type.DETAILED
|
|
},
|
|
'Size in DB': {
|
|
value: 0,
|
|
type: StatsGenerator.Type.BYTE
|
|
}
|
|
};
|
|
|
|
const getFilesCountAndSize = async () => {
|
|
const uploads = await db.table('files').select('size');
|
|
|
|
return {
|
|
'Total': uploads.length,
|
|
'Size in DB': {
|
|
value: uploads.reduce((acc, upload) => acc + parseInt(upload.size, 10), 0),
|
|
type: StatsGenerator.Type.BYTE
|
|
}
|
|
};
|
|
};
|
|
|
|
const getImagesCount = async () => {
|
|
const Images = await db.table('files')
|
|
.where('type', 'like', `image/%`)
|
|
.count('id as count')
|
|
.then(rows => rows[0].count);
|
|
|
|
return { Images };
|
|
};
|
|
|
|
const getVideosCount = async () => {
|
|
const Videos = await db.table('files')
|
|
.where('type', 'like', `video/%`)
|
|
.count('id as count')
|
|
.then(rows => rows[0].count);
|
|
|
|
return { Videos };
|
|
};
|
|
|
|
const getOthersCount = async () => {
|
|
// rename to key, value from type, count
|
|
const data = await db.table('files')
|
|
.select('type as key')
|
|
.count('id as value')
|
|
.whereNot('type', 'like', `image/%`)
|
|
.whereNot('type', 'like', `video/%`)
|
|
.groupBy('key')
|
|
.orderBy('value', 'desc');
|
|
|
|
const count = data.reduce((acc, val) => acc + val.value, 0);
|
|
|
|
return {
|
|
Others: {
|
|
data,
|
|
count,
|
|
type: StatsGenerator.Type.DETAILED
|
|
}
|
|
};
|
|
};
|
|
|
|
const result = await Promise.all([getFilesCountAndSize(), getImagesCount(), getVideosCount(), getOthersCount()]);
|
|
|
|
return { ...stats, ...Object.assign({}, ...result) };
|
|
}
|
|
|
|
static async getUsersInfo(db) {
|
|
const stats = {
|
|
Total: 0,
|
|
Admins: 0,
|
|
Disabled: 0
|
|
};
|
|
|
|
const users = await db.table('users');
|
|
stats.Total = users.length;
|
|
|
|
for (const user of users) {
|
|
if (!user.enabled) {
|
|
stats.Disabled++;
|
|
}
|
|
|
|
if (user.isAdmin) {
|
|
stats.Admins++;
|
|
}
|
|
}
|
|
|
|
return stats;
|
|
}
|
|
|
|
static async getAlbumStats(db) {
|
|
const stats = {
|
|
'Total': 0,
|
|
'NSFW': 0,
|
|
'Generated archives': 0,
|
|
'Generated identifiers': 0,
|
|
'Files in albums': 0
|
|
};
|
|
|
|
const albums = await db.table('albums');
|
|
stats.Total = albums.length;
|
|
for (const album of albums) {
|
|
if (album.nsfw) stats.NSFW++;
|
|
if (album.zipGeneratedAt) stats['Generated archives']++; // XXX: Bobby checks each after if a zip really exists on the disk. Is it really needed?
|
|
}
|
|
|
|
stats['Generated identifiers'] = await db.table('albumsLinks').count('id as count').then(rows => rows[0].count);
|
|
stats['Files in albums'] = await db.table('albumsFiles')
|
|
.whereNotNull('albumId')
|
|
.count('id as count')
|
|
.then(rows => rows[0].count);
|
|
|
|
return stats;
|
|
}
|
|
|
|
static async getStats(db) {
|
|
const res = {};
|
|
|
|
for (const [name, funct] of Object.entries(StatsGenerator.statGenerators)) {
|
|
res[name] = await funct(db);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
static async getMissingStats(db, existingStats) {
|
|
const res = {};
|
|
|
|
for (const [name, funct] of Object.entries(StatsGenerator.statGenerators)) {
|
|
if (existingStats.indexOf(name) === -1) res[name] = await funct(db);
|
|
}
|
|
|
|
return res;
|
|
}
|
|
}
|
|
|
|
module.exports = StatsGenerator;
|