feat: add Wails runtime library and initial application setup

- Created package.json for Wails JavaScript runtime library.
- Added TypeScript definitions for runtime functions.
- Implemented runtime.js with event handling and logging functions.
- Initialized Go module with dependencies for Wails and other libraries.
- Added main.go for application entry point and asset embedding.
- Configured wails.json for application settings and build commands.
This commit is contained in:
Jonathan Atta
2026-03-11 17:06:52 +01:00
commit 17beab746e
34 changed files with 3608 additions and 0 deletions

107
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,107 @@
import React, { useEffect, useState } from 'react'
import { EventsOn, EventsOff } from '../wailsjs/runtime/runtime'
type ProcessInfo = {
pid: number
name: string
cpu: number
mem: number
}
type Metrics = {
cpu_percent: number
total_mem: number
free_mem: number
processes: ProcessInfo[]
timestamp: number
gpu_name?: string
gpu_total_mem?: number
gpu_used_mem?: number
gpu_util_percent?: number
}
export default function App() {
const [metrics, setMetrics] = useState<Metrics | null>(null)
useEffect(() => {
const handler = (m: Metrics) => setMetrics(m)
EventsOn('metrics', handler)
return () => EventsOff('metrics')
}, [])
// No simulator: real metrics only
return (
<div className="app bg-black min-h-screen text-neon">
<header className="p-6 flex items-center justify-between">
<h1 className="text-3xl font-extrabold tracking-tight">sysmon</h1>
<div className="text-sm opacity-80">Realtime system monitor</div>
</header>
<main className="p-6 grid grid-cols-3 gap-6">
<section className="col-span-1 bg-[#071422] p-4 rounded-lg border border-[#2b2d42]/40">
<h2 className="text-lg font-semibold mb-2">Overview</h2>
{metrics ? (
<div>
<div>CPU: {metrics.cpu_percent.toFixed(1)}%</div>
<div>Memory: {((metrics.total_mem - metrics.free_mem) / (1024 * 1024)).toFixed(0)} MB used</div>
<div>Memory free: {(metrics.free_mem / (1024 * 1024)).toFixed(0)} MB</div>
{metrics.processes && metrics.processes.length > 0 && (
<div>
Top memory: {
(() => {
const top = [...metrics.processes].sort((a, b) => b.mem - a.mem)[0]
return `${top.name} (${(top.mem / 1024 / 1024).toFixed(1)} MB)`
})()
}
</div>
)}
<div>Last: {new Date(metrics.timestamp).toLocaleTimeString()}</div>
</div>
) : (
<div>Waiting for metrics</div>
)}
</section>
<section className="col-span-1 bg-[#071422] p-4 rounded-lg border border-[#2b2d42]/40">
<h2 className="text-lg font-semibold mb-2">GPU</h2>
{metrics ? (
<div>
<div>Name: {metrics.gpu_name || 'N/A'}</div>
<div>GPU Memory: {metrics.gpu_used_mem ? (metrics.gpu_used_mem / 1024 / 1024).toFixed(0) + ' MB used / ' + (metrics.gpu_total_mem! / 1024 / 1024).toFixed(0) + ' MB' : 'N/A'}</div>
<div>Util: {metrics.gpu_util_percent ? metrics.gpu_util_percent.toFixed(1) + '%' : 'N/A'}</div>
</div>
) : (
<div>Waiting for GPU</div>
)}
</section>
<section className="col-span-2 bg-[#071422] p-4 rounded-lg border border-[#2b2d42]/40">
<h2 className="text-lg font-semibold mb-2">Top processes</h2>
<div className="overflow-auto max-h-[60vh]">
<table className="w-full table-auto">
<thead>
<tr className="text-left text-sm opacity-80">
<th>PID</th>
<th>Name</th>
<th>CPU</th>
<th>Mem</th>
</tr>
</thead>
<tbody>
{metrics?.processes.map((p) => (
<tr key={p.pid} className="odd:bg-black/20">
<td>{p.pid}</td>
<td>{p.name}</td>
<td>{p.cpu.toFixed(1)}%</td>
<td>{(p.mem / 1024 / 1024).toFixed(1)} MB</td>
</tr>
))}
</tbody>
</table>
</div>
</section>
</main>
</div>
)
}

10
frontend/src/main.tsx Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react'
import { createRoot } from 'react-dom/client'
import App from './App'
import './styles.css'
createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
)

15
frontend/src/styles.css Normal file
View File

@@ -0,0 +1,15 @@
:root{
--neon: #39ff14;
}
body{font-family: Inter, ui-sans-serif, system-ui; background:#000; color:#e6eef0}
.text-neon{color:var(--neon)}
/* Minimal layout for dev without Tailwind */
.app{min-height:100vh}
header{padding:1.5rem; display:flex; align-items:center; justify-content:space-between}
main{padding:1.5rem; display:grid; grid-template-columns:1fr 2fr; gap:1.5rem}
section{background:#071422; padding:1rem; border-radius:0.5rem; border:1px solid rgba(43,45,66,0.4)}
table{width:100%; border-collapse:collapse}
thead tr{opacity:0.85; font-size:0.9rem}
tbody tr:nth-child(odd){background:rgba(0,0,0,0.08)}

View File

@@ -0,0 +1,21 @@
type Handler = (m: any) => void
export function EventsOn(event: string, handler: Handler) {
const wrap = (e: CustomEvent) => handler(e.detail)
// store reference so it can be removed
;(handler as any).__wrap = wrap
window.addEventListener(event, wrap as EventListener)
}
export function EventsOff(event: string, handler: Handler) {
const wrap = (handler as any).__wrap
if (wrap) {
window.removeEventListener(event, wrap as EventListener)
delete (handler as any).__wrap
}
}
// Helper to simulate events from dev environment
export function Emit(event: string, payload: any) {
window.dispatchEvent(new CustomEvent(event, { detail: payload }))
}