305 lines
9.8 KiB
QML
305 lines
9.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
|
|
property bool outputVisible: true
|
|
|
|
// 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
|
|
}
|
|
}
|
|
|
|
// Resizable split between process list and output
|
|
SplitView {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
orientation: Qt.Vertical
|
|
|
|
// Process list
|
|
ListView {
|
|
id: procList
|
|
SplitView.fillWidth: true
|
|
SplitView.minimumHeight: 80
|
|
SplitView.preferredHeight: 180
|
|
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)"
|
|
outputVisible = true
|
|
}
|
|
}
|
|
|
|
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 (toggle-able)
|
|
GroupBox {
|
|
id: outputPane
|
|
visible: outputVisible
|
|
SplitView.fillWidth: true
|
|
SplitView.fillHeight: true
|
|
SplitView.minimumHeight: 60
|
|
title: selectedPid > 0 ? "Output (PID " + selectedPid + ")" : "Output"
|
|
|
|
ScrollView {
|
|
anchors.fill: parent
|
|
TextArea {
|
|
id: outputArea
|
|
text: outputText
|
|
readOnly: true
|
|
font.family: "monospace"
|
|
font.pointSize: 11
|
|
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)"
|
|
outputVisible = true
|
|
}
|
|
}
|
|
|
|
Button {
|
|
text: outputVisible ? "Hide Output" : "Show Output"
|
|
icon.name: outputVisible ? "go-up" : "go-down"
|
|
onClicked: outputVisible = !outputVisible
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|