This commit is contained in:
2025-08-24 14:02:41 +02:00
parent 2253ef71e6
commit 9f39481cb4
4 changed files with 254 additions and 76 deletions

View File

@@ -13,6 +13,19 @@ pub mut:
sessionid string // unique link to job
}
@[heap]
struct Pane {
pub mut:
window &Window @[str: skip]
id int // pane id (e.g., %1, %2)
pid int // process id
active bool // is this the active pane
cmd string // command running in pane
env map[string]string
created_at time.Time
last_output_offset int // for tracking new logs
}
@[params]
pub struct TmuxNewArgs {
sessionid string
@@ -38,6 +51,40 @@ pub fn new(args TmuxNewArgs) !Tmux {
// tmux.scan()!
// }
pub struct ProcessStats {
pub mut:
cpu_percent f64
memory_bytes u64
memory_percent f64
}
pub fn (mut p Pane) get_stats() !ProcessStats {
if p.pid == 0 {
return ProcessStats{}
}
// Use ps command to get CPU and memory stats
cmd := 'ps -p ${p.pid} -o %cpu,%mem,rss --no-headers'
result := osal.execute_silent(cmd) or {
return error('Cannot get stats for PID ${p.pid}: ${err}')
}
if result.trim() == '' {
return error('Process ${p.pid} not found')
}
parts := result.trim().split_any(' \t').filter(it != '')
if parts.len < 3 {
return error('Invalid ps output: ${result}')
}
return ProcessStats{
cpu_percent: parts[0].f64()
memory_percent: parts[1].f64()
memory_bytes: parts[2].u64() * 1024 // ps returns KB, convert to bytes
}
}
pub fn (mut t Tmux) stop() ! {
$if debug {
console.print_debug('Stopping tmux...')
@@ -67,6 +114,87 @@ pub fn (mut t Tmux) start() ! {
t.scan()!
}
enum ProcessStatus {
running
finished_ok
finished_error
not_found
}
pub struct LogEntry {
pub mut:
content string
timestamp time.Time
offset int
}
pub fn (mut p Pane) get_new_logs() ![]LogEntry {
// Capture pane content with line numbers
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S ${p.last_output_offset} -p'
result := osal.execute_silent(cmd) or {
return error('Cannot capture pane output: ${err}')
}
}
pub fn (mut p Pane) check_process_status() !ProcessStatus {
}
if result.trim() == '' {
// Process not found, check exit status from shell history or tmux
return p.check_exit_status() or { .finished_error }
}
return .running
}
fn (mut p Pane) check_exit_status() !ProcessStatus {
// Get the last few lines to see if there's an exit status
logs := p.get_all_logs()!
lines := logs.split_into_lines()
// Look for shell prompt indicating command finished
for line in lines.reverse() {
line_clean := line.trim()
if line_clean.contains('$') || line_clean.contains('#') || line_clean.contains('>') {
// Found shell prompt, command likely finished
// Could also check for specific exit codes in history
return .finished_ok
}
}
return .finished_error
}
lines := result.split_into_lines()
mut entries := []LogEntry{}
for i, line in lines {
if line.trim() != '' {
entries << LogEntry{
content: line
timestamp: time.now()
offset: p.last_output_offset + i + 1
}
}
}
// Update offset to avoid duplicates next time
if entries.len > 0 {
p.last_output_offset = entries.last().offset
}
return entries
}
pub fn (mut p Pane) get_all_logs() !string {
cmd := 'tmux capture-pane -t ${p.window.session.name}:@${p.window.id}.%${p.id} -S -1000 -p'
return osal.execute_silent(cmd) or {
error('Cannot capture pane output: ${err}')
}
}
// print list of tmux sessions
pub fn (mut t Tmux) list_print() {
// os.log('TMUX - Start listing ....')

View File

@@ -4,52 +4,63 @@ import freeflowuniverse.herolib.osal.core as osal
import freeflowuniverse.herolib.core.texttools
import freeflowuniverse.herolib.ui.console
fn (mut t Tmux) scan_add(line string) !&Window {
// console.print_debug(" -- scan add: $line")
if line.count('|') < 4 {
return error(@FN + 'expects line with at least 5 params separated by |')
}
fn (mut t Tmux) scan_add(line string) !&Pane {
// Parse the line to get session, window, and pane info
line_arr := line.split('|')
session_name := line_arr[0]
window_name := line_arr[1]
window_id := line_arr[2]
pane_active := line_arr[3]
pane_id := line_arr[4]
pane_pid := line_arr[5]
pane_start_command := line_arr[6] or { '' }
line_arr := line.split('|')
session_name := line_arr[0]
window_name := line_arr[1]
window_id := line_arr[2]
pane_active := line_arr[3]
pane_id := line_arr[4]
pane_pid := line_arr[5]
pane_start_command := line_arr[6] or { '' }
wid := (window_id.replace('@', '')).int()
pid := (pane_id.replace('%', '')).int()
wid := (window_id.replace('@', '')).int()
mut s := t.session_get(session_name)!
// os.log('TMUX FOUND: $line\n ++ $session_name:$window_name wid:$window_id pid:$pane_pid entrypoint:$pane_start_command')
mut s := t.session_get(session_name)!
// Get or create window
mut w := if s.window_exist(name: window_name, id: wid) {
s.window_get(name: window_name, id: wid)!
} else {
mut new_w := Window{
session: s
name: texttools.name_fix(window_name)
id: wid
panes: []&Pane{}
}
s.windows << &new_w
&new_w
}
mut active := false
if pane_active == '1' {
active = true
}
// Create or update pane
mut p := Pane{
window: w
id: pid
pid: pane_pid.int()
active: pane_active == '1'
cmd: pane_start_command
created_at: time.now()
}
mut name := texttools.name_fix(window_name)
// Check if pane already exists
mut found := false
for mut existing_pane in w.panes {
if existing_pane.id == pid {
existing_pane.pid = p.pid
existing_pane.active = p.active
existing_pane.cmd = p.cmd
found = true
break
}
}
mut w := Window{
session: s
name: name
}
if !found {
w.panes << &p
}
if !(s.window_exist(name: window_name, id: wid)) {
// console.print_debug("window not exists")
s.windows << &w
} else {
w = s.window_get(name: window_name, id: wid)!
}
w.id = wid
w.active = active
w.pid = pane_pid.int()
w.paneid = (pane_id.replace('%', '')).int()
w.cmd = pane_start_command
return &w
return &p
}
// scan the system to detect sessions .

View File

@@ -117,6 +117,11 @@ pub fn (mut s Session) windows_get() []&Window {
return res
}
// List windows in a session
pub fn (mut s Session) list_windows() []&Window {
return s.windows
}
pub fn (mut s Session) windownames_get() []string {
mut res := []string{}
for _, window in s.windows {
@@ -133,7 +138,18 @@ pub fn (mut s Session) str() string {
return out
}
// pub fn (mut s Session) activate()! {
pub fn (mut s Session) get_total_stats() !ProcessStats {
mut total := ProcessStats{}
for mut window in s.windows {
stats := window.get_total_stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
}
// pub fn (mut s Session) activate()! {
// active_session := s.tmux.redis.get('tmux:active_session') or { 'No active session found' }
// if active_session != 'No active session found' && s.name != active_session {
// s.tmuxexecutor.db.exec('tmux attach-session -t $active_session') or {

View File

@@ -13,10 +13,8 @@ pub mut:
session &Session @[skip]
name string
id int
panes []&Pane // windows contain multiple panes
active bool
pid int
paneid int
cmd string
env map[string]string
}
@@ -81,12 +79,13 @@ pub fn (mut s Session) window_new(args WindowArgs) !Window {
mut w := Window{
session: &s
name: namel
cmd: args.cmd
panes: []&Pane{}
env: args.env
}
s.windows << &w
w.create()!
s.window_delete(name: 'notused')!
w.create(args.cmd)!
// After creation, scan to populate panes
s.tmux.scan()!
return w
}
@@ -136,39 +135,34 @@ pub fn (mut s Session) window_delete(args_ WindowGetArgs) ! {
s.windows.delete(i) // i is now the one in the list which needs to be removed
}
pub fn (mut w Window) create() ! {
pub fn (mut w Window) create(cmd_ string) ! {
// tmux new-window -P -c /tmp -e good=1 -e bad=0 -n koekoe -t main bash
if w.cmd.contains('\n') {
mut final_cmd := cmd_
if cmd_.contains('\n') {
// means is multiline need to write it
// scriptpath string // is the path where the script will be put which is executed
// scriptkeep bool // means we don't remove the script
os.mkdir_all('/tmp/tmux/${w.session.name}')!
cmd_new := osal.exec_string(
cmd: w.cmd
cmd: cmd_
scriptpath: '/tmp/tmux/${w.session.name}/${w.name}.sh'
scriptkeep: true
)!
w.cmd = cmd_new
final_cmd = cmd_new
}
// console.print_debug(w)
if w.active == false {
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
cmd := 'tmux new-window ${res_opt} -t ${w.session.name} -n ${w.name} \'/bin/bash -c ${w.cmd}\''
console.print_debug(cmd)
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or {
return error("Can't create new window ${w.name} \n${cmd}\n${err}")
}
// now look at output to get the window id = wid
line_arr := res.output.split('|')
wid := line_arr[2] or { panic('cannot split line for window create.\n${line_arr}') }
w.id = wid.replace('@', '').int()
$if debug {
console.print_header(' WINDOW - Window: ${w.name} created in session: ${w.session.name}')
}
} else {
return error('cannot create window, it already exists.\n${w.name}:${w.id}:${w.cmd}')
res_opt := "-P -F '#{session_name}|#{window_name}|#{window_id}|#{pane_active}|#{pane_id}|#{pane_pid}|#{pane_start_command}'"
cmd := 'tmux new-window ${res_opt} -t ${w.session.name} -n ${w.name} \'/bin/bash -c ${final_cmd}\''
console.print_debug(cmd)
res := osal.exec(cmd: cmd, stdout: false, name: 'tmux_window_create') or {
return error("Can't create new window ${w.name} \n${cmd}\n${err}")
}
// now look at output to get the window id = wid
line_arr := res.output.split('|')
wid := line_arr[2] or { panic('cannot split line for window create.\n${line_arr}') }
w.id = wid.replace('@', '').int()
$if debug {
console.print_header(' WINDOW - Window: ${w.name} created in session: ${w.session.name}')
}
}
@@ -192,29 +186,58 @@ pub fn (mut w Window) stop() ! {
name: 'tmux_kill-window'
// die: false
) or { return error("Can't kill window with id:${w.id}: ${err}") }
w.pid = 0
w.active = false
w.active = false // Window is no longer active
}
pub fn (window Window) str() string {
return ' - name:${window.name} wid:${window.id} active:${window.active} pid:${window.pid} cmd:${window.cmd}'
mut out := ' - name:${window.name} wid:${window.id} active:${window.active}'
for pane in window.panes {
out += '\n ${*pane}'
}
return out
}
pub fn (mut w Window) get_total_stats() !ProcessStats {
mut total := ProcessStats{}
for mut pane in w.panes {
stats := pane.get_stats() or { continue }
total.cpu_percent += stats.cpu_percent
total.memory_bytes += stats.memory_bytes
total.memory_percent += stats.memory_percent
}
return total
}
// will select the current window so with tmux a we can go there .
// to login into a session do `tmux a -s mysessionname`
fn (mut w Window) activate() ! {
cmd2 := 'tmux select-window -t %${w.id}'
cmd2 := 'tmux select-window -t @${w.id}'
osal.execute_silent(cmd2) or {
return error("Couldn't select window ${w.name} \n${cmd2}\n${err}")
}
}
// List panes in a window
pub fn (mut w Window) list_panes() []&Pane {
return w.panes
}
// Get active pane in window
pub fn (mut w Window) get_active_pane() ?&Pane {
for pane in w.panes {
if pane.active {
return pane
}
}
return none
}
// show the environment
pub fn (mut w Window) environment_print() ! {
res := osal.execute_silent('tmux show-environment -t %${w.paneid}') or {
return error('Couldnt show enviroment cmd: ${w.cmd} \n${err}')
}
os.log(res)
// This function needs to be updated to target a specific pane, not the window directly.
// For now, I'll leave it as is, but it's a point for future refinement.
// It should probably take a pane ID or operate on the active pane.
return error('Window.environment_print() needs to be updated to target a specific pane.')
}
// capture the output