iiab-tools/android/dashboard/sockets/home.socket.ts

126 lines
4.6 KiB
TypeScript
Raw Normal View History

2026-03-26 22:58:16 +00:00
import { Socket } from 'socket.io';
import os from 'os';
import { exec } from 'child_process';
import util from 'util';
const execPromise = util.promisify(exec);
async function getSystemStats() {
let stats = {
hostname: os.hostname(),
ip: 'Unknown',
uptime: os.uptime(),
disk: { total: 0, used: 0, free: 0, percent: 0 },
ram: { total: 0, used: 0, percent: 0 },
swap: { total: 0, used: 0, percent: 0 }
};
// 1. GET IP (Plan A: Native | Plan B: raw ifconfig)
try {
const nets = os.networkInterfaces();
for (const name of Object.keys(nets)) {
if (nets[name]) {
for (const net of nets[name]) {
if (net.family === 'IPv4' && !net.internal) {
stats.ip = net.address; break;
}
}
}
if (stats.ip !== 'Unknown') break;
}
} catch (e) {}
// Architect's Plan B: Your ifconfig pipeline silencing the proot error
if (stats.ip === 'Unknown') {
try {
// The 2>/dev/null sends the "Warning: Permission denied" to the abyss
const { stdout } = await execPromise("ifconfig 2>/dev/null | grep inet | grep -v 127.0.0.1 | awk '{print $2}'");
const ips = stdout.trim().split('\n');
if (ips.length > 0 && ips[0]) {
stats.ip = ips[0]; // We take the first valid IP it spits out
}
} catch (e) {
console.log('[Home] Plan B (ifconfig) failed or yielded no results.');
}
}
// 2. GET DISK (df -k /)
try {
const { stdout } = await execPromise('df -k /');
const lines = stdout.trim().split('\n');
if (lines.length > 1) {
const parts = lines[1].trim().split(/\s+/);
const total = parseInt(parts[1]) * 1024;
const used = parseInt(parts[2]) * 1024;
stats.disk = {
total, used, free: total - used,
percent: Math.round((used / total) * 100)
};
}
} catch (e) {}
// 3. GET RAM AND SWAP (free -b)
try {
const { stdout } = await execPromise('free -b');
const lines = stdout.trim().split('\n');
// Parse RAM (Line 2) -> Mem: total used free ...
if (lines.length > 1) {
const ramParts = lines[1].trim().split(/\s+/);
const rTotal = parseInt(ramParts[1]);
const rUsed = parseInt(ramParts[2]);
stats.ram = { total: rTotal, used: rUsed, percent: Math.round((rUsed / rTotal) * 100) };
}
// Parse Swap (Line 3) -> Swap: total used free ...
if (lines.length > 2) {
const swapParts = lines[2].trim().split(/\s+/);
const sTotal = parseInt(swapParts[1]);
const sUsed = parseInt(swapParts[2]);
// Avoid division by zero if there is no Swap configured
stats.swap = { total: sTotal, used: sUsed, percent: sTotal > 0 ? Math.round((sUsed / sTotal) * 100) : 0 };
}
} catch (e) {
console.log('[Home] Error reading memory (free -b). Using limited native data.');
// Ultra-safe Node native fallback (Only gives RAM, no Swap)
stats.ram.total = os.totalmem();
const free = os.freemem();
stats.ram.used = stats.ram.total - free;
stats.ram.percent = Math.round((stats.ram.used / stats.ram.total) * 100);
}
return stats;
}
function formatBytes(bytes: number) {
if (bytes === 0 || isNaN(bytes)) return '0 B';
const k = 1024;
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
export const handleHomeEvents = (socket: Socket) => {
socket.on('request_home_stats', async () => {
const stats = await getSystemStats();
const uiData = {
hostname: stats.hostname,
ip: stats.ip,
uptime: Math.floor(stats.uptime / 60) + ' min',
diskTotal: formatBytes(stats.disk.total),
diskUsed: formatBytes(stats.disk.used),
diskFree: formatBytes(stats.disk.free),
diskPercent: stats.disk.percent,
ramUsed: formatBytes(stats.ram.used),
ramTotal: formatBytes(stats.ram.total),
ramPercent: stats.ram.percent,
swapUsed: formatBytes(stats.swap.used),
swapTotal: formatBytes(stats.swap.total),
swapPercent: stats.swap.percent
};
socket.emit('home_stats_ready', uiData);
});
};