ollie/kde/gui/ProcManagerWindow.qml

287 lines
8.8 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import QtQuick.Window
Window {
id: procWindow
title: "Background Processes" + (currentSessionName ? " — " + currentSessionName : "")
width: 650
height: 450
minimumWidth: 400
minimumHeight: 300
property var procs: []
property string outputText: ""
property int selectedPid: -1
// Track the session we're displaying (synced from main window)
property string currentSessionId: ollie.activeSessionId
property string currentAgentId: ollie.activeAgentId
property string currentSessionName: ""
function refresh() {
if (!currentSessionId || !currentAgentId) {
procs = []
return
}
var rawProcs = ollie.listProcs(currentSessionId, currentAgentId)
// Sort by PID descending (newest first)
rawProcs.sort(function(a, b) { return b.pid - a.pid })
procs = rawProcs
}
function updateSessionName() {
if (currentSessionId) {
var cfg = ollie.readSessionConfig(currentSessionId)
currentSessionName = cfg.name || currentSessionId
} else {
currentSessionName = ""
}
}
Timer {
id: refreshTimer
interval: 2000
running: procWindow.visible
repeat: true
onTriggered: refresh()
}
onVisibleChanged: {
if (visible) {
currentSessionId = ollie.activeSessionId
currentAgentId = ollie.activeAgentId
updateSessionName()
refresh()
}
}
// Sync to active session/agent changes
Connections {
target: ollie
function onActiveSessionIdChanged() {
if (procWindow.visible) {
currentSessionId = ollie.activeSessionId
currentAgentId = ollie.activeAgentId
updateSessionName()
selectedPid = -1
outputText = ""
refresh()
}
}
function onActiveAgentIdChanged() {
if (procWindow.visible) {
currentAgentId = ollie.activeAgentId
selectedPid = -1
outputText = ""
refresh()
}
}
function onProcStarted(sessionId, agentId, payload) {
if (procWindow.visible && sessionId === currentSessionId && agentId === currentAgentId) {
refresh()
}
}
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 8
// Header showing current session/agent
RowLayout {
Layout.fillWidth: true
spacing: 8
Label {
text: "Session:"
font.bold: true
}
Label {
text: currentSessionName || "(none)"
Layout.fillWidth: true
elide: Text.ElideMiddle
}
}
// Process list
ListView {
id: procList
Layout.fillWidth: true
Layout.preferredHeight: 180
Layout.minimumHeight: 100
model: procs
clip: true
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
delegate: Rectangle {
width: procList.width
height: 44
color: selectedPid === modelData.pid ? palette.highlight : (index % 2 ? palette.alternateBase : palette.base)
MouseArea {
anchors.fill: parent
onClicked: selectedPid = modelData.pid
onDoubleClicked: {
selectedPid = modelData.pid
var out = ollie.procOutput(currentSessionId, currentAgentId, selectedPid)
outputText = out.length > 0 ? out : "(no output)"
}
}
RowLayout {
anchors.fill: parent
anchors.margins: 8
spacing: 12
// Status indicator
Rectangle {
width: 10
height: 10
radius: 5
color: modelData.status === "running" ? "#4CAF50" : (modelData.exitCode === 0 ? "#9E9E9E" : "#F44336")
}
// PID
Label {
text: modelData.pid
font.family: "monospace"
Layout.preferredWidth: 50
}
// Tool
Label {
text: modelData.tool
font.weight: Font.Medium
Layout.preferredWidth: 80
elide: Text.ElideRight
}
// Command
Label {
text: modelData.cmd
Layout.fillWidth: true
elide: Text.ElideMiddle
opacity: 0.8
}
// Runtime
Label {
text: formatRuntime(modelData.runtime)
font.family: "monospace"
Layout.preferredWidth: 55
horizontalAlignment: Text.AlignRight
}
// Exit code (if exited)
Label {
visible: modelData.status !== "running"
text: modelData.exitCode === 0 ? "✓" : "exit " + modelData.exitCode
font.family: "monospace"
color: modelData.exitCode === 0 ? "#4CAF50" : "#F44336"
Layout.preferredWidth: 45
}
}
}
Label {
anchors.centerIn: parent
visible: procs.length === 0
text: currentSessionId ? "No background processes" : "No session selected"
opacity: 0.5
}
}
// Output pane (always visible)
GroupBox {
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumHeight: 100
title: selectedPid > 0 ? "Output (PID " + selectedPid + ")" : "Output"
ScrollView {
anchors.fill: parent
TextArea {
id: outputArea
text: outputText
readOnly: true
font.family: "monospace"
font.pointSize: 9
wrapMode: TextEdit.Wrap
selectByMouse: true
placeholderText: "Select a process and click View Output"
}
}
}
// Action buttons
RowLayout {
Layout.fillWidth: true
spacing: 8
Button {
text: "Refresh"
icon.name: "view-refresh"
onClicked: refresh()
}
Button {
text: "View Output"
icon.name: "document-preview"
enabled: selectedPid > 0
onClicked: {
outputText = ""
var out = ollie.procOutput(currentSessionId, currentAgentId, selectedPid)
outputText = out.length > 0 ? out : "(no output)"
}
}
Item { Layout.fillWidth: true }
Button {
text: "Terminate"
icon.name: "process-stop"
enabled: selectedPid > 0 && isRunning(selectedPid)
onClicked: {
ollie.procTerm(currentSessionId, currentAgentId, selectedPid)
Qt.callLater(refresh)
}
}
Button {
text: "Kill"
icon.name: "edit-delete"
enabled: selectedPid > 0 && isRunning(selectedPid)
onClicked: {
ollie.procKill(currentSessionId, currentAgentId, selectedPid)
Qt.callLater(refresh)
}
}
Button {
text: "Dismiss"
icon.name: "window-close"
onClicked: procWindow.close()
}
}
}
function formatRuntime(seconds) {
if (seconds < 60) return seconds.toFixed(1) + "s"
if (seconds < 3600) return Math.floor(seconds / 60) + "m " + Math.floor(seconds % 60) + "s"
return Math.floor(seconds / 3600) + "h " + Math.floor((seconds % 3600) / 60) + "m"
}
function isRunning(pid) {
for (var i = 0; i < procs.length; i++) {
if (procs[i].pid === pid) {
return procs[i].status === "running"
}
}
return false
}
}