...
This commit is contained in:
@@ -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 ....')
|
||||
|
||||
@@ -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 .
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user