Merge branch 'development_tmux' of github.com:freeflowuniverse/herolib into development_tmux

This commit is contained in:
2025-08-25 12:04:43 +02:00
3 changed files with 273 additions and 215 deletions

View File

@@ -15,11 +15,12 @@ const ttyd_port = 7890
fn show_help() { fn show_help() {
println('=== Tmux Server Dashboard ===') println('=== Tmux Server Dashboard ===')
println('Usage:') println('Usage:')
println(' ${os.args[0]} # Start the dashboard') println(' ${os.args[0]} # Start the dashboard')
println(' ${os.args[0]} -down # Stop dashboard and cleanup') println(' ${os.args[0]} -editable # Start dashboard with editable ttyd')
println(' ${os.args[0]} -status # Show dashboard status') println(' ${os.args[0]} -down # Stop dashboard and cleanup')
println(' ${os.args[0]} -restart # Restart the dashboard') println(' ${os.args[0]} -status # Show dashboard status')
println(' ${os.args[0]} -help # Show this help') println(' ${os.args[0]} -restart # Restart the dashboard')
println(' ${os.args[0]} -help # Show this help')
println('') println('')
println('Dashboard includes:') println('Dashboard includes:')
println(' Python HTTP Server (port ${python_port})') println(' Python HTTP Server (port ${python_port})')
@@ -27,6 +28,10 @@ fn show_help() {
println(' Hero Web (compile and run hero web server)') println(' Hero Web (compile and run hero web server)')
println(' CPU Monitor (htop)') println(' CPU Monitor (htop)')
println(' Web access via ttyd (port ${ttyd_port})') println(' Web access via ttyd (port ${ttyd_port})')
println('')
println('ttyd modes:')
println(' Default: read-only access to terminal')
println(' -editable: allows writing/editing in the terminal')
} }
fn stop_dashboard() ! { fn stop_dashboard() ! {
@@ -87,25 +92,25 @@ fn show_status() ! {
} }
println(' Window "${window_name}" exists with ${window.panes.len} panes') println(' Window "${window_name}" exists with ${window.panes.len} panes')
// Show pane details // Show pane details
for i, pane in window.panes { for i, pane in window.panes {
service_name := match i { service_name := match i {
0 { 'Python HTTP Server' } 0 { 'Python HTTP Server' }
1 { 'Counter Service' } 1 { 'Counter Service' }
2 { 'Hero Web Service' } 2 { 'Hero Web Service' }
3 { 'CPU Monitor' } 3 { 'CPU Monitor' }
else { 'Service ${i+1}' } else { 'Service ${i + 1}' }
}
mut pane_mut := pane
stats := pane_mut.stats() or {
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, PID=${pane.pid} (stats unavailable)')
continue
}
memory_mb := f64(stats.memory_bytes) / (1024.0 * 1024.0)
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, CPU=${stats.cpu_percent:.1f}%, Memory=${memory_mb:.1f}MB')
} }
mut pane_mut := pane
stats := pane_mut.stats() or {
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, PID=${pane.pid} (stats unavailable)')
continue
}
memory_mb := f64(stats.memory_bytes) / (1024.0 * 1024.0)
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, CPU=${stats.cpu_percent:.1f}%, Memory=${memory_mb:.1f}MB')
}
} else { } else {
println(' Tmux session "${session_name}" not running') println(' Tmux session "${session_name}" not running')
} }
@@ -139,7 +144,7 @@ fn restart_dashboard() ! {
start_dashboard()! start_dashboard()!
} }
fn start_dashboard() ! { fn start_dashboard_with_mode(ttyd_editable bool) ! {
println('=== Server Dashboard with 4 Panes ===') println('=== Server Dashboard with 4 Panes ===')
println('Setting up tmux session with:') println('Setting up tmux session with:')
println(' 1. Python HTTP Server (port ${python_port})') println(' 1. Python HTTP Server (port ${python_port})')
@@ -148,172 +153,172 @@ fn start_dashboard() ! {
println(' 4. CPU Monitor (htop)') println(' 4. CPU Monitor (htop)')
println('') println('')
// Initialize tmux // Initialize tmux
mut t := tmux.new()! mut t := tmux.new()!
if !t.is_running()! { if !t.is_running()! {
println('Starting tmux server...') println('Starting tmux server...')
t.start()! t.start()!
} }
// Clean up existing session if it exists // Clean up existing session if it exists
if t.session_exist(session_name) { if t.session_exist(session_name) {
println('Cleaning up existing ${session_name} session...') println('Cleaning up existing ${session_name} session...')
t.session_delete(session_name)! t.session_delete(session_name)!
} }
// Create new session // Create new session
println('Creating ${session_name} session...') println('Creating ${session_name} session...')
mut session := t.session_create(name: session_name)! mut session := t.session_create(name: session_name)!
// Create main window with initial bash shell // Create main window with initial bash shell
println('Creating dashboard window...') println('Creating dashboard window...')
mut window := session.window_new(name: window_name, cmd: 'bash', reset: true)! mut window := session.window_new(name: window_name, cmd: 'bash', reset: true)!
// Wait for initial setup // Wait for initial setup
time.sleep(500 * time.millisecond) time.sleep(500 * time.millisecond)
t.scan()! t.scan()!
println('\n=== Setting up 4-pane layout ===') println('\n=== Setting up 4-pane layout ===')
// Get the main window // Get the main window
window = session.window_get(name: window_name)! window = session.window_get(name: window_name)!
// Split horizontally first (left and right halves) // Split horizontally first (left and right halves)
println('1. Splitting horizontally for left/right layout...') println('1. Splitting horizontally for left/right layout...')
mut right_pane := window.pane_split_horizontal('bash')! mut right_pane := window.pane_split_horizontal('bash')!
time.sleep(300 * time.millisecond)
window.scan()!
// Split left pane vertically (top-left and bottom-left)
println('2. Splitting left pane vertically...')
window.scan()!
if window.panes.len >= 2 {
mut left_pane := window.panes[0] // First pane should be the left one
left_pane.select()!
time.sleep(200 * time.millisecond)
mut bottom_left_pane := window.pane_split_vertical('bash')!
time.sleep(300 * time.millisecond) time.sleep(300 * time.millisecond)
window.scan()! window.scan()!
}
// Split right pane vertically (top-right and bottom-right) // Split left pane vertically (top-left and bottom-left)
println('3. Splitting right pane vertically...') println('2. Splitting left pane vertically...')
window.scan()!
if window.panes.len >= 3 {
// Find the rightmost pane (should be the last one after horizontal split)
mut right_pane_current := window.panes[window.panes.len - 1]
right_pane_current.select()!
time.sleep(200 * time.millisecond)
mut bottom_right_pane := window.pane_split_vertical('bash')!
time.sleep(300 * time.millisecond)
window.scan()! window.scan()!
} if window.panes.len >= 2 {
mut left_pane := window.panes[0] // First pane should be the left one
// Set a proper 2x2 tiled layout using tmux command left_pane.select()!
println('4. Setting 2x2 tiled layout...') time.sleep(200 * time.millisecond)
os.execute('tmux select-layout -t ${session_name}:${window_name} tiled') mut bottom_left_pane := window.pane_split_vertical('bash')!
time.sleep(500 * time.millisecond) time.sleep(300 * time.millisecond)
window.scan()! window.scan()!
println('5. Layout complete! We now have 4 panes in 2x2 grid.')
// Refresh to get all panes
window.scan()!
println('\nCurrent panes: ${window.panes.len}')
for i, pane in window.panes {
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}')
}
if window.panes.len < 4 {
eprintln('Expected 4 panes, but got ${window.panes.len}')
exit(1)
}
println('\n=== Starting services in each pane ===')
// Pane 1 (top-left): Python HTTP Server
println('Starting Python HTTP Server in pane 1...')
mut pane1 := window.panes[0]
pane1.select()!
pane1.send_command('echo "=== Python HTTP Server Port 8000 ==="')!
pane1.send_command('cd /tmp && python3 -m http.server ${python_port}')!
time.sleep(500 * time.millisecond)
// Pane 2 (bottom-left): Counter Service
println('Starting Counter Service in pane 2...')
mut pane2 := window.panes[1]
pane2.select()!
pane2.send_command('echo "=== Counter Service - Updates every 5 seconds ==="')!
pane2.send_command('while true; do echo "Count: $(date)"; sleep 5; done')!
time.sleep(500 * time.millisecond)
// Pane 3 (top-right): Hero Web
println('Starting Hero Web in pane 3...')
mut pane3 := window.panes[2]
pane3.select()!
pane3.send_command('echo "=== Hero Web Server ==="')!
pane3.send_command('./cli/compile.vsh && /Users/mahmoud/hero/bin/hero web')!
time.sleep(500 * time.millisecond)
// Pane 4 (bottom-right): CPU Monitor
println('Starting CPU Monitor in pane 4...')
mut pane4 := window.panes[3]
pane4.select()!
pane4.send_command('echo "=== CPU Monitor ==="')!
pane4.send_command('htop')!
println('\n=== All services started! ===')
// Wait a moment for services to initialize
time.sleep(2000 * time.millisecond)
// Refresh and show current state
t.scan()!
window = session.window_get(name: window_name)!
println('\n=== Current Dashboard State ===')
for i, mut pane in window.panes {
stats := pane.stats() or {
println(' Pane ${i + 1}: ID=%${pane.id}, PID=${pane.pid} (stats unavailable)')
continue
} }
memory_mb := f64(stats.memory_bytes) / (1024.0 * 1024.0)
service_name := match i {
0 { 'Python Server' }
1 { 'Counter Service' }
2 { 'Hero Web' }
3 { 'CPU Monitor' }
else { 'Unknown' }
}
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, CPU=${stats.cpu_percent:.1f}%, Memory=${memory_mb:.1f}MB')
}
println('\n=== Access Information ===') // Split right pane vertically (top-right and bottom-right)
println(' Python HTTP Server: http://localhost:${python_port}') println('3. Splitting right pane vertically...')
println(' Tmux Session: tmux attach-session -t ${session_name}') window.scan()!
println('') if window.panes.len >= 3 {
println('=== Pane Resize Commands ===') // Find the rightmost pane (should be the last one after horizontal split)
println('To resize panes, attach to the session and use:') mut right_pane_current := window.panes[window.panes.len - 1]
println(' Ctrl+B then Arrow Keys (hold Ctrl+B and press arrow keys)') right_pane_current.select()!
println(' Or programmatically:') time.sleep(200 * time.millisecond)
for i, pane in window.panes { mut bottom_right_pane := window.pane_split_vertical('bash')!
service_name := match i { time.sleep(300 * time.millisecond)
0 { 'Python Server' } window.scan()!
1 { 'Counter Service' } }
2 { 'Hero Web' }
3 { 'CPU Monitor' } // Set a proper 2x2 tiled layout using tmux command
else { 'Unknown' } println('4. Setting 2x2 tiled layout...')
os.execute('tmux select-layout -t ${session_name}:${window_name} tiled')
time.sleep(500 * time.millisecond)
window.scan()!
println('5. Layout complete! We now have 4 panes in 2x2 grid.')
// Refresh to get all panes
window.scan()!
println('\nCurrent panes: ${window.panes.len}')
for i, pane in window.panes {
println(' Pane ${i}: ID=%${pane.id}, PID=${pane.pid}')
}
if window.panes.len < 4 {
eprintln('Expected 4 panes, but got ${window.panes.len}')
exit(1)
}
println('\n=== Starting services in each pane ===')
// Pane 1 (top-left): Python HTTP Server
println('Starting Python HTTP Server in pane 1...')
mut pane1 := window.panes[0]
pane1.select()!
pane1.send_command('echo "=== Python HTTP Server Port 8000 ==="')!
pane1.send_command('cd /tmp && python3 -m http.server ${python_port}')!
time.sleep(500 * time.millisecond)
// Pane 2 (bottom-left): Counter Service
println('Starting Counter Service in pane 2...')
mut pane2 := window.panes[1]
pane2.select()!
pane2.send_command('echo "=== Counter Service - Updates every 5 seconds ==="')!
pane2.send_command('while true; do echo "Count: $(date)"; sleep 5; done')!
time.sleep(500 * time.millisecond)
// Pane 3 (top-right): Hero Web
println('Starting Hero Web in pane 3...')
mut pane3 := window.panes[2]
pane3.select()!
pane3.send_command('echo "=== Hero Web Server ==="')!
pane3.send_command('hero web')!
time.sleep(500 * time.millisecond)
// Pane 4 (bottom-right): CPU Monitor
println('Starting CPU Monitor in pane 4...')
mut pane4 := window.panes[3]
pane4.select()!
pane4.send_command('echo "=== CPU Monitor ==="')!
pane4.send_command('htop')!
println('\n=== All services started! ===')
// Wait a moment for services to initialize
time.sleep(2000 * time.millisecond)
// Refresh and show current state
t.scan()!
window = session.window_get(name: window_name)!
println('\n=== Current Dashboard State ===')
for i, mut pane in window.panes {
stats := pane.stats() or {
println(' Pane ${i + 1}: ID=%${pane.id}, PID=${pane.pid} (stats unavailable)')
continue
}
memory_mb := f64(stats.memory_bytes) / (1024.0 * 1024.0)
service_name := match i {
0 { 'Python Server' }
1 { 'Counter Service' }
2 { 'Hero Web' }
3 { 'CPU Monitor' }
else { 'Unknown' }
}
println(' Pane ${i + 1} (${service_name}): ID=%${pane.id}, CPU=${stats.cpu_percent:.1f}%, Memory=${memory_mb:.1f}MB')
}
println('\n=== Access Information ===')
println(' Python HTTP Server: http://localhost:${python_port}')
println(' Tmux Session: tmux attach-session -t ${session_name}')
println('')
println('=== Pane Resize Commands ===')
println('To resize panes, attach to the session and use:')
println(' Ctrl+B then Arrow Keys (hold Ctrl+B and press arrow keys)')
println(' Or programmatically:')
for i, pane in window.panes {
service_name := match i {
0 { 'Python Server' }
1 { 'Counter Service' }
2 { 'Hero Web' }
3 { 'CPU Monitor' }
else { 'Unknown' }
}
println(' # Resize ${service_name} pane:')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -U 5 # Up')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -D 5 # Down')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -L 5 # Left')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -R 5 # Right')
} }
println(' # Resize ${service_name} pane:')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -U 5 # Up')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -D 5 # Down')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -L 5 # Left')
println(' tmux resize-pane -t ${session_name}:${window_name}.%${pane.id} -R 5 # Right')
}
println('\n=== Dashboard is running! ===') println('\n=== Dashboard is running! ===')
println('Attach to view: tmux attach-session -t ${session_name}') println('Attach to view: tmux attach-session -t ${session_name}')
@@ -321,44 +326,64 @@ for i, pane in window.panes {
println('To stop all services: tmux kill-session -t ${session_name}') println('To stop all services: tmux kill-session -t ${session_name}')
println('Running the browser-based dashboard: TTYD') println('Running the browser-based dashboard: TTYD')
window.run_ttyd(ttyd_port) or { println('Failed to start ttyd: ${err}') } mode_str := if ttyd_editable { 'editable' } else { 'read-only' }
println('Starting ttyd in ${mode_str} mode...')
window.run_ttyd(port: ttyd_port, editable: ttyd_editable) or {
println('Failed to start ttyd: ${err}')
}
} }
// Main execution with argument handling fn start_dashboard() ! {
if os.args.len > 1 { start_dashboard_with_mode(false)!
command := os.args[1] }
match command {
'-down' { fn main() {
stop_dashboard() or { mut ttyd_editable := false // Local flag for ttyd editable mode
eprintln('Error stopping dashboard: ${err}')
// Main execution with argument handling
if os.args.len > 1 {
command := os.args[1]
match command {
'-editable' {
ttyd_editable = true
start_dashboard_with_mode(ttyd_editable) or {
eprintln('Error starting dashboard: ${err}')
exit(1)
}
}
'-down' {
stop_dashboard() or {
eprintln('Error stopping dashboard: ${err}')
exit(1)
}
}
'-status' {
show_status() or {
eprintln('Error getting status: ${err}')
exit(1)
}
}
'-restart' {
restart_dashboard() or {
eprintln('Error restarting dashboard: ${err}')
exit(1)
}
}
'-help', '--help', '-h' {
show_help()
}
else {
eprintln('Unknown command: ${command}')
show_help()
exit(1) exit(1)
} }
} }
'-status' { } else {
show_status() or { // No arguments - start the dashboard
eprintln('Error getting status: ${err}') start_dashboard_with_mode(ttyd_editable) or {
exit(1) eprintln('Error starting dashboard: ${err}')
}
}
'-restart' {
restart_dashboard() or {
eprintln('Error restarting dashboard: ${err}')
exit(1)
}
}
'-help', '--help', '-h' {
show_help()
}
else {
eprintln('Unknown command: ${command}')
show_help()
exit(1) exit(1)
} }
} }
} else {
// No arguments - start the dashboard
start_dashboard() or {
eprintln('Error starting dashboard: ${err}')
exit(1)
}
} }

View File

@@ -260,14 +260,27 @@ pub fn (mut s Session) stop() ! {
} }
// Run ttyd for this session so it can be accessed in the browser // Run ttyd for this session so it can be accessed in the browser
pub fn (mut s Session) run_ttyd(port int) ! { pub fn (mut s Session) run_ttyd(args TtydArgs) ! {
target := '${s.name}' target := '${s.name}'
cmd := 'nohup ttyd -p ${port} tmux attach -t ${target} >/dev/null 2>&1 &'
// Add -W flag for write access if editable mode is enabled
mut ttyd_flags := '-p ${args.port}'
if args.editable {
ttyd_flags += ' -W'
}
cmd := 'nohup ttyd ${ttyd_flags} tmux attach -t ${target} >/dev/null 2>&1 &'
code := os.system(cmd) code := os.system(cmd)
if code != 0 { if code != 0 {
return error('Failed to start ttyd on port ${port} for session ${s.name}') return error('Failed to start ttyd on port ${args.port} for session ${s.name}')
} }
println('ttyd started for session ${s.name} at http://localhost:${port}') mode_str := if args.editable { 'editable' } else { 'read-only' }
println('ttyd started for session ${s.name} at http://localhost:${args.port} (${mode_str} mode)')
}
// Backward compatibility method - runs ttyd in read-only mode
pub fn (mut s Session) run_ttyd_readonly(port int) ! {
s.run_ttyd(port: port, editable: false)!
} }

View File

@@ -257,15 +257,35 @@ pub fn (mut w Window) pane_split_vertical(cmd string) !&Pane {
return w.pane_split(cmd: cmd, horizontal: false) return w.pane_split(cmd: cmd, horizontal: false)
} }
@[params]
pub struct TtydArgs {
pub mut:
port int
editable bool // if true, allows write access to the terminal
}
// Run ttyd for this window so it can be accessed in the browser // Run ttyd for this window so it can be accessed in the browser
pub fn (mut w Window) run_ttyd(port int) ! { pub fn (mut w Window) run_ttyd(args TtydArgs) ! {
target := '${w.session.name}:@${w.id}' target := '${w.session.name}:@${w.id}'
cmd := 'nohup ttyd -p ${port} tmux attach -t ${target} >/dev/null 2>&1 &'
// Add -W flag for write access if editable mode is enabled
mut ttyd_flags := '-p ${args.port}'
if args.editable {
ttyd_flags += ' -W'
}
cmd := 'nohup ttyd ${ttyd_flags} tmux attach -t ${target} >/dev/null 2>&1 &'
code := os.system(cmd) code := os.system(cmd)
if code != 0 { if code != 0 {
return error('Failed to start ttyd on port ${port} for window ${w.name}') return error('Failed to start ttyd on port ${args.port} for window ${w.name}')
} }
println('ttyd started for window ${w.name} at http://localhost:${port}') mode_str := if args.editable { 'editable' } else { 'read-only' }
println('ttyd started for window ${w.name} at http://localhost:${args.port} (${mode_str} mode)')
}
// Backward compatibility method - runs ttyd in read-only mode
pub fn (mut w Window) run_ttyd_readonly(port int) ! {
w.run_ttyd(port: port, editable: false)!
} }