- Create shared command utility in ts/utils/command.ts - Update index.ts, process-manager.ts, system-info.ts to use shared utility - Remove duplicate runCommand implementations from all files - Remove legacy Chrome wrapper methods (startChrome, stopChrome, isChromeRunning) - Consolidate Sway window selectors from 5 to 2 - Remove unused isobuild TypeScript files (mod.ts, deno.json, ts/index.ts) - Make getStatus() async to properly await system info - Add disk and system info sections to UI
223 lines
5.5 KiB
TypeScript
223 lines
5.5 KiB
TypeScript
/**
|
|
* System Information
|
|
*
|
|
* Gathers CPU, RAM, disk, network, and GPU information
|
|
*/
|
|
|
|
import { runCommand } from '../utils/command.ts';
|
|
|
|
export interface CpuInfo {
|
|
model: string;
|
|
cores: number;
|
|
usage: number;
|
|
}
|
|
|
|
export interface MemoryInfo {
|
|
total: number;
|
|
used: number;
|
|
free: number;
|
|
usagePercent: number;
|
|
}
|
|
|
|
export interface DiskInfo {
|
|
device: string;
|
|
mountpoint: string;
|
|
total: number;
|
|
used: number;
|
|
free: number;
|
|
usagePercent: number;
|
|
}
|
|
|
|
export interface NetworkInterface {
|
|
name: string;
|
|
ip: string;
|
|
mac: string;
|
|
state: 'up' | 'down';
|
|
}
|
|
|
|
export interface GpuInfo {
|
|
name: string;
|
|
driver: string;
|
|
}
|
|
|
|
export interface SystemInfoData {
|
|
hostname: string;
|
|
cpu: CpuInfo;
|
|
memory: MemoryInfo;
|
|
disks: DiskInfo[];
|
|
network: NetworkInterface[];
|
|
gpu: GpuInfo[];
|
|
uptime: number;
|
|
}
|
|
|
|
export class SystemInfo {
|
|
async getInfo(): Promise<SystemInfoData> {
|
|
const [hostname, cpu, memory, disks, network, gpu, uptime] =
|
|
await Promise.all([
|
|
this.getHostname(),
|
|
this.getCpuInfo(),
|
|
this.getMemoryInfo(),
|
|
this.getDiskInfo(),
|
|
this.getNetworkInfo(),
|
|
this.getGpuInfo(),
|
|
this.getUptime(),
|
|
]);
|
|
|
|
return { hostname, cpu, memory, disks, network, gpu, uptime };
|
|
}
|
|
|
|
private async getHostname(): Promise<string> {
|
|
try {
|
|
const result = await runCommand('hostname', []);
|
|
if (!result.success) return 'unknown';
|
|
return result.stdout.trim();
|
|
} catch {
|
|
return 'unknown';
|
|
}
|
|
}
|
|
|
|
private async getCpuInfo(): Promise<CpuInfo> {
|
|
try {
|
|
const cpuinfo = await Deno.readTextFile('/proc/cpuinfo');
|
|
const modelMatch = cpuinfo.match(/model name\s*:\s*(.+)/);
|
|
const coreMatches = cpuinfo.match(/processor\s*:/g);
|
|
|
|
// Get CPU usage from /proc/stat
|
|
const stat = await Deno.readTextFile('/proc/stat');
|
|
const cpuLine = stat.split('\n')[0];
|
|
const values = cpuLine.split(/\s+/).slice(1).map(Number);
|
|
const total = values.reduce((a, b) => a + b, 0);
|
|
const idle = values[3];
|
|
const usage = ((total - idle) / total) * 100;
|
|
|
|
return {
|
|
model: modelMatch ? modelMatch[1] : 'Unknown',
|
|
cores: coreMatches ? coreMatches.length : 1,
|
|
usage: Math.round(usage * 10) / 10,
|
|
};
|
|
} catch {
|
|
return { model: 'Unknown', cores: 1, usage: 0 };
|
|
}
|
|
}
|
|
|
|
private async getMemoryInfo(): Promise<MemoryInfo> {
|
|
try {
|
|
const meminfo = await Deno.readTextFile('/proc/meminfo');
|
|
const totalMatch = meminfo.match(/MemTotal:\s*(\d+)/);
|
|
const freeMatch = meminfo.match(/MemAvailable:\s*(\d+)/);
|
|
|
|
const total = totalMatch ? parseInt(totalMatch[1], 10) * 1024 : 0;
|
|
const free = freeMatch ? parseInt(freeMatch[1], 10) * 1024 : 0;
|
|
const used = total - free;
|
|
|
|
return {
|
|
total,
|
|
used,
|
|
free,
|
|
usagePercent: total > 0 ? Math.round((used / total) * 1000) / 10 : 0,
|
|
};
|
|
} catch {
|
|
return { total: 0, used: 0, free: 0, usagePercent: 0 };
|
|
}
|
|
}
|
|
|
|
private async getDiskInfo(): Promise<DiskInfo[]> {
|
|
try {
|
|
const result = await runCommand('df', ['-B1', '--output=source,target,size,used,avail']);
|
|
if (!result.success) return [];
|
|
const lines = result.stdout.trim().split('\n').slice(1);
|
|
const disks: DiskInfo[] = [];
|
|
|
|
for (const line of lines) {
|
|
const parts = line.trim().split(/\s+/);
|
|
if (parts.length >= 5 && parts[0].startsWith('/dev/')) {
|
|
const total = parseInt(parts[2], 10);
|
|
const used = parseInt(parts[3], 10);
|
|
const free = parseInt(parts[4], 10);
|
|
|
|
disks.push({
|
|
device: parts[0],
|
|
mountpoint: parts[1],
|
|
total,
|
|
used,
|
|
free,
|
|
usagePercent: total > 0 ? Math.round((used / total) * 1000) / 10 : 0,
|
|
});
|
|
}
|
|
}
|
|
|
|
return disks;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private async getNetworkInfo(): Promise<NetworkInterface[]> {
|
|
try {
|
|
const cmdResult = await runCommand('ip', ['-j', 'addr']);
|
|
if (!cmdResult.success) return [];
|
|
const interfaces = JSON.parse(cmdResult.stdout);
|
|
const result: NetworkInterface[] = [];
|
|
|
|
for (const iface of interfaces) {
|
|
if (iface.ifname === 'lo') continue;
|
|
|
|
let ip = '';
|
|
let mac = iface.address || '';
|
|
|
|
for (const addr of iface.addr_info || []) {
|
|
if (addr.family === 'inet') {
|
|
ip = addr.local;
|
|
break;
|
|
}
|
|
}
|
|
|
|
result.push({
|
|
name: iface.ifname,
|
|
ip: ip || 'No IP',
|
|
mac,
|
|
state: iface.operstate === 'UP' ? 'up' : 'down',
|
|
});
|
|
}
|
|
|
|
return result;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private async getGpuInfo(): Promise<GpuInfo[]> {
|
|
try {
|
|
const result = await runCommand('lspci', ['-mm']);
|
|
if (!result.success) return [];
|
|
const lines = result.stdout.split('\n');
|
|
const gpus: GpuInfo[] = [];
|
|
|
|
for (const line of lines) {
|
|
if (line.includes('VGA') || line.includes('3D') || line.includes('Display')) {
|
|
const parts = line.split('"');
|
|
if (parts.length >= 6) {
|
|
gpus.push({
|
|
name: parts[5] || 'Unknown GPU',
|
|
driver: 'unknown',
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
return gpus;
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
private async getUptime(): Promise<number> {
|
|
try {
|
|
const uptime = await Deno.readTextFile('/proc/uptime');
|
|
return Math.floor(parseFloat(uptime.split(' ')[0]));
|
|
} catch {
|
|
return 0;
|
|
}
|
|
}
|
|
}
|