Watch
1
0
Fork
You've already forked RedFlag
0
RedFlag/desktop/qml/Main.qml
Fimeg f487de554a desktop: become the machine console
Qt/QML now carries 11 local views while the Agent owns observation and intent. Tauri, WebKit, and the second React desktop build leave together. Linux ships first; Windows waits for a native Qt runner.
2026-09-01 08:29:58 -04:00

1012 lines
82 KiB
QML

import QtQuick
import QtQuick.Controls.Basic
import QtQuick.Layouts
import com.redflag.desktop 1.0
ApplicationWindow {
id: app
title: "RedFlag"
width: 1320
height: 860
minimumWidth: 960
minimumHeight: 640
visible: true
color: Theme.window
property string page: "overview"
property var history: parse(machine.historyJson, [])
property var system: parse(machine.systemJson, {})
property var processes: parse(machine.processesJson, [])
property var connections: parse(machine.connectionsJson, [])
property var containers: parse(machine.containersJson, [])
property var services: parse(machine.servicesJson, [])
property var updates: parse(machine.updatesJson, [])
property var security: parse(machine.securityJson, {})
property var events: parse(machine.eventsJson, [])
property var processDetail: parse(machine.processDetailJson, {})
property var softwareSnapshot: parse(machine.softwareJson, {})
property var software: softwareSnapshot.packages || []
property var softwareDetail: parse(machine.softwareDetailJson, {})
property var selectedUpdate: ({})
property string pendingProcessQuery: ""
property var approval: parse(machine.approvalJson, {})
function parse(raw, fallback) {
try { return JSON.parse(raw || "") } catch (_) { return fallback }
}
function bytes(value) {
const n = Number(value || 0)
if (n <= 0) return "0 B"
const units = ["B", "KiB", "MiB", "GiB", "TiB"]
const i = Math.min(units.length - 1, Math.floor(Math.log(n) / Math.log(1024)))
const amount = n / Math.pow(1024, i)
return (amount >= 10 || i === 0 ? amount.toFixed(0) : amount.toFixed(1)) + " " + units[i]
}
function percent(value) { return Number(value || 0).toFixed(0) + "%" }
function endpoint(address, port) { return (address || "*") + ":" + Number(port || 0) }
// The Agent reports the kernel's full container ID; the container inventory
// carries the 12-character short form. The join is a prefix, never equality.
function containerFor(id) {
if (!id) return null
for (let i = 0; i < containers.length; ++i)
if (containers[i].container_id && String(id).indexOf(String(containers[i].container_id)) === 0)
return containers[i]
return null
}
function ownerLabel(p) {
if (!p) return ""
if (p.container_id) {
const c = containerFor(p.container_id)
return c && c.name ? c.name : (p.container_runtime || "container") + " " + String(p.container_id).substring(0, 12)
}
return p.unit || ""
}
function ownerColor(p) { return p && p.container_id ? Theme.cyan : Theme.blue }
// Services report the unit name with .service stripped; processes carry it whole.
function unitProcessCount(name) {
if (!name) return 0
const unit = name + ".service"
let n = 0
for (let i = 0; i < processes.length; ++i)
if (processes[i].unit === unit) n++
return n
}
// The capabilities that turn "a process" into "a process that can rewrite the
// machine". Tinted so a wall of chips still reads at a glance.
readonly property var sharpCapabilities: ["CAP_SYS_ADMIN", "CAP_SYS_MODULE", "CAP_SYS_RAWIO", "CAP_SYS_PTRACE",
"CAP_SYS_BOOT", "CAP_BPF", "CAP_NET_ADMIN", "CAP_NET_RAW", "CAP_DAC_READ_SEARCH", "CAP_SETUID", "CAP_SETGID"]
// CapEff arrives as a 64-bit hex mask. Split it into two 32-bit words rather
// than trusting a single parseInt, which loses precision above bit 52.
function holdsSharpCapability(mask) {
if (!mask) return false
const hex = String(mask)
const low = parseInt(hex.slice(-8) || "0", 16) || 0
const high = parseInt(hex.slice(0, -8) || "0", 16) || 0
// low word: DAC_READ_SEARCH 2, SETGID 6, SETUID 7, NET_ADMIN 12,
// SYS_MODULE 16, SYS_RAWIO 17, SYS_PTRACE 19, SYS_ADMIN 21, SYS_BOOT 22
const lowMask = (1 << 2) | (1 << 6) | (1 << 7) | (1 << 12) | (1 << 16) | (1 << 17) | (1 << 19) | (1 << 21) | (1 << 22)
// high word: PERFMON 38, BPF 39 → bits 6 and 7 of the high word
const highMask = (1 << 6) | (1 << 7)
return (low & lowMask) !== 0 || (high & highMask) !== 0
}
function sharpProcessCount() {
let n = 0
for (let i = 0; i < processes.length; ++i)
if (holdsSharpCapability(processes[i].capabilities_effective)) n++
return n
}
function capabilityColor(name) { return sharpCapabilities.indexOf(name) >= 0 ? Theme.warn : Theme.dim }
// The four stages the fleet dashboard shows, driven here by what the Agent
// actually returned rather than by a hardcoded chip.
function stageState(index) {
const a = approval
const policy = a.policy || {}
if (index === 0) return "done"
if (a.error) return index === 1 ? "failed" : "idle"
if (machine.approvalRunning) return index === 1 ? "running" : "idle"
if (a.request_id === undefined) return "idle"
if (index === 1) return "done"
if (index === 2) return policy.token_id ? "done" : "failed"
if (index === 3) return policy.executed ? "done" : "failed"
return "idle"
}
function stageColor(state) {
if (state === "done") return Theme.good
if (state === "running") return Theme.warn
if (state === "failed") return Theme.bad
return Theme.faint
}
function locallyApprovable(u) {
return !!u && (u.package_type === "dnf" || u.package_type === "apt")
}
function approvalBlockReason(u) {
if (!u || !u.package_name) return ""
if (machine.enrollment === "fleet enrolled")
return "This machine is fleet enrolled — authority belongs to the RedFlag Server, and the Agent refuses a local mint."
if (!locallyApprovable(u))
return "Local approval covers DNF and APT, the ecosystems whose closure the Agent can resolve and pin from signed repository metadata. " + String(u.package_type || "this ecosystem").toUpperCase() + " still executes through the fleet signed-command path."
return ""
}
function processCountLabel(n) { return n ? n + (n === 1 ? " process" : " processes") : "" }
function containerProcessCount(id) {
if (!id) return 0
let n = 0
for (let i = 0; i < processes.length; ++i)
if (processes[i].container_id && String(processes[i].container_id).indexOf(String(id)) === 0) n++
return n
}
function historyCeiling(first, second) {
let maximum = 1
for (let i = 0; i < history.length; ++i)
maximum = Math.max(maximum, Number(history[i][first] || 0) + Number(history[i][second] || 0))
return maximum
}
function navBadge(key) {
if (key === "processes") return String(machine.processCount)
if (key === "containers") return String(machine.containerCount)
if (key === "services") return String(machine.serviceCount)
if (key === "software") return String(machine.softwareCount)
if (key === "updates") return String(machine.updateCount)
if (key === "security" && machine.criticalCount > 0) return String(machine.criticalCount)
return ""
}
function pageComponent() {
switch (page) {
case "performance": return performancePage
case "processes": return processesPage
case "network": return networkPage
case "storage": return storagePage
case "containers": return containersPage
case "services": return servicesPage
case "software": return softwarePage
case "updates": return updatesPage
case "security": return securityPage
case "history": return historyPage
default: return overviewPage
}
}
Machine {
id: machine
onProcessDetailUpdated: processDialog.open()
onSoftwareDetailUpdated: softwareDialog.open()
onApprovalFinished: {
machine.refreshOverview()
machine.refreshSoftware()
}
}
Component.onCompleted: {
machine.refreshTelemetry()
machine.refreshOverview()
machine.refreshProcesses()
machine.refreshConnections()
machine.refreshSoftware()
}
// Containers and Services join against the process list; entering either page
// refreshes it once rather than polling a full snapshot every few seconds.
onPageChanged: {
if (page === "processes" || page === "containers" || page === "services" || page === "security") machine.refreshProcesses()
if (page === "network") machine.refreshConnections()
if (page === "software") machine.refreshSoftware()
}
Timer { interval: 1000; repeat: true; running: true; onTriggered: machine.refreshTelemetry() }
Timer { interval: 15000; repeat: true; running: true; onTriggered: machine.refreshOverview() }
Timer {
interval: 3000; repeat: true; running: true
onTriggered: {
if (app.page === "processes") machine.refreshProcesses()
if (app.page === "network") machine.refreshConnections()
}
}
RowLayout {
anchors.fill: parent
spacing: 0
Rectangle {
Layout.preferredWidth: Theme.railWidth
Layout.fillHeight: true
color: Theme.rail
border.width: 0
ColumnLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 4
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 54
Layout.leftMargin: 4
spacing: 10
Image { width: 30; height: 30; source: "qrc:/qt/qml/com/redflag/desktop/icons/icon.png"; fillMode: Image.PreserveAspectFit; smooth: true }
Column {
Text { text: "RedFlag"; color: Theme.text; font.pixelSize: 19; font.weight: 750 }
Text { text: "THIS MACHINE"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1.4 }
}
Item { Layout.fillWidth: true }
}
Text { text: "OBSERVE"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1.2; Layout.leftMargin: 8; Layout.topMargin: 8; Layout.bottomMargin: 2 }
Repeater {
model: [
{ key: "overview", label: "Overview" },
{ key: "performance", label: "Performance" },
{ key: "processes", label: "Processes" },
{ key: "network", label: "Network" },
{ key: "storage", label: "Storage" }
]
NavItem {
required property var modelData
Layout.fillWidth: true
label: modelData.label
badge: app.navBadge(modelData.key)
selected: app.page === modelData.key
onClicked: app.page = modelData.key
}
}
Text { text: "OPERATE"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1.2; Layout.leftMargin: 8; Layout.topMargin: 10; Layout.bottomMargin: 2 }
Repeater {
model: [
{ key: "containers", label: "Containers" },
{ key: "services", label: "Services" },
{ key: "software", label: "Software" },
{ key: "updates", label: "Updates" }
]
NavItem {
required property var modelData
Layout.fillWidth: true
label: modelData.label
badge: app.navBadge(modelData.key)
selected: app.page === modelData.key
onClicked: app.page = modelData.key
}
}
Text { text: "TRUST"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1.2; Layout.leftMargin: 8; Layout.topMargin: 10; Layout.bottomMargin: 2 }
Repeater {
model: [
{ key: "security", label: "Security" },
{ key: "history", label: "History" }
]
NavItem {
required property var modelData
Layout.fillWidth: true
label: modelData.label
badge: app.navBadge(modelData.key)
selected: app.page === modelData.key
onClicked: app.page = modelData.key
}
}
Item { Layout.fillHeight: true }
Rectangle { Layout.fillWidth: true; height: 1; color: Theme.divider }
RowLayout {
Layout.fillWidth: true
Layout.preferredHeight: 46
Layout.leftMargin: 5
Rectangle { width: 8; height: 8; radius: 4; color: machine.connected ? Theme.good : Theme.bad }
Column {
Text { text: machine.connected ? "Agent connected" : "Agent unavailable"; color: Theme.dim; font.pixelSize: 11 }
Text { text: machine.enrollment + (machine.agentVersion.length ? " · v" + machine.agentVersion : ""); color: Theme.faint; font.pixelSize: 9 }
}
}
}
}
Rectangle { Layout.preferredWidth: 1; Layout.fillHeight: true; color: Theme.divider }
ColumnLayout {
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: Theme.headerHeight
color: Theme.window
border.width: 0
RowLayout {
anchors.fill: parent
anchors.leftMargin: 24
anchors.rightMargin: 22
spacing: 14
Rectangle {
width: 10; height: 10; radius: 5
color: !machine.connected ? Theme.faint : machine.healthState === "healthy" ? Theme.good : Theme.warn
}
ColumnLayout {
spacing: 1
Text { text: machine.hostname; color: Theme.text; font.pixelSize: Theme.fontHeading; font.weight: 650 }
Text { text: machine.machineSubtitle; color: Theme.dim; font.pixelSize: Theme.fontMeta }
}
Rectangle {
width: healthLabel.implicitWidth + 18; height: 25; radius: 13
color: Theme.tint(machine.healthState === "healthy" ? Theme.good : machine.healthState === "degraded" ? Theme.warn : Theme.faint, 0.14)
Text { id: healthLabel; anchors.centerIn: parent; text: machine.healthState.toUpperCase(); color: machine.healthState === "healthy" ? Theme.good : machine.healthState === "degraded" ? Theme.warn : Theme.faint; font.pixelSize: 9; font.weight: 700; font.letterSpacing: 1 }
}
Item { Layout.fillWidth: true }
Text { text: machine.uptime.length ? "UP " + machine.uptime.toUpperCase() : ""; color: Theme.faint; font.pixelSize: 9; font.family: Theme.mono }
Rectangle {
width: 32; height: 32; radius: 16; color: refreshMouse.containsMouse ? Theme.hover : "transparent"
Text { anchors.centerIn: parent; text: "↻"; color: Theme.dim; font.pixelSize: 20 }
MouseArea { id: refreshMouse; anchors.fill: parent; hoverEnabled: true; onClicked: { machine.refreshTelemetry(); machine.refreshOverview() } }
}
Rectangle {
width: 32; height: 32; radius: 16; color: themeMouse.containsMouse ? Theme.hover : "transparent"
Text { anchors.centerIn: parent; text: Theme.dark ? "◐" : "◑"; color: Theme.dim; font.pixelSize: 15 }
MouseArea { id: themeMouse; anchors.fill: parent; hoverEnabled: true; onClicked: Theme.dark = !Theme.dark }
}
}
Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider }
}
Rectangle {
visible: !machine.connected && machine.connectionError.length > 0
Layout.fillWidth: true
Layout.preferredHeight: visible ? errorText.implicitHeight + 20 : 0
color: Theme.redDim
Text { id: errorText; anchors.fill: parent; anchors.margins: 10; text: machine.connectionError; color: Theme.bad; font.pixelSize: 11; font.family: Theme.mono; wrapMode: Text.Wrap }
}
Loader {
Layout.fillWidth: true
Layout.fillHeight: true
sourceComponent: app.pageComponent()
}
}
}
Component {
id: overviewPage
ScrollView {
clip: true
contentWidth: availableWidth
ColumnLayout {
width: parent.width
spacing: 16
anchors.margins: 22
Text { text: "This machine"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700; Layout.topMargin: 20 }
Text { text: "Operational condition, live load, and the systems asking for attention."; color: Theme.dim; font.pixelSize: Theme.fontBody }
GridLayout {
Layout.fillWidth: true; columns: 4; columnSpacing: 12; rowSpacing: 12
MetricCard { Layout.fillWidth: true; label: "CPU"; value: app.percent(machine.cpuUsage); detail: "load " + machine.load1.toFixed(2); accent: Theme.pressure(machine.cpuUsage) }
MetricCard { Layout.fillWidth: true; label: "Memory"; value: app.percent(machine.memoryPercent); detail: app.bytes(machine.memoryUsed) + " / " + app.bytes(machine.memoryTotal); accent: Theme.pressure(machine.memoryPercent) }
MetricCard { Layout.fillWidth: true; label: "Network"; value: "↓ " + app.bytes(machine.networkReceive) + "/s"; detail: "↑ " + app.bytes(machine.networkTransmit) + "/s"; accent: Theme.cyan }
MetricCard { Layout.fillWidth: true; label: "Temperature"; value: machine.temperature > 0 ? machine.temperature.toFixed(0) + " °C" : "Unavailable"; detail: machine.temperature > 0 ? "hottest reported sensor" : "no sensor evidence"; accent: machine.temperature >= 90 ? Theme.bad : Theme.purple }
MetricCard { Layout.fillWidth: true; label: "Updates"; value: String(machine.updateCount); detail: machine.criticalCount + " critical"; accent: machine.criticalCount > 0 ? Theme.bad : machine.updateCount > 0 ? Theme.warn : Theme.good }
MetricCard { Layout.fillWidth: true; label: "Containers"; value: machine.containerRunning + " / " + machine.containerCount; detail: machine.containerUnhealthy + " unhealthy"; accent: machine.containerUnhealthy > 0 ? Theme.bad : Theme.good }
MetricCard { Layout.fillWidth: true; label: "Services"; value: machine.serviceRunning + " / " + machine.serviceCount; detail: machine.serviceFailed + " failed"; accent: machine.serviceFailed > 0 ? Theme.bad : Theme.good }
MetricCard { Layout.fillWidth: true; label: "Processes"; value: String(machine.processCount); detail: machine.agentStatus + " · " + machine.enrollment; accent: Theme.blue }
}
RowLayout {
Layout.fillWidth: true; Layout.preferredHeight: 250; spacing: 12
Rectangle {
Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
Text { text: "CPU — LAST " + app.history.length + " SECONDS"; color: Theme.faint; font.pixelSize: 10; font.letterSpacing: 1; anchors.left: parent.left; anchors.leftMargin: 16; anchors.top: parent.top; anchors.topMargin: 14 }
Text { text: app.percent(machine.cpuUsage); color: Theme.text; font.pixelSize: 28; font.weight: 650; anchors.right: parent.right; anchors.rightMargin: 16; anchors.top: parent.top; anchors.topMargin: 10 }
LineGraph { anchors.fill: parent; anchors.margins: 16; anchors.topMargin: 54; values: app.history.map(p => p.cpu_percent); ceiling: 100; lineColor: Theme.red }
}
Rectangle {
Layout.preferredWidth: 360; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
Text { text: "RECENT"; color: Theme.faint; font.pixelSize: 10; font.letterSpacing: 1; anchors.left: parent.left; anchors.leftMargin: 16; anchors.top: parent.top; anchors.topMargin: 14 }
ListView {
anchors.fill: parent; anchors.topMargin: 40; clip: true; model: app.events.slice(0, 6)
delegate: Rectangle {
required property var modelData; width: ListView.view.width; height: 35; color: "transparent"
Rectangle { width: 5; height: 5; radius: 3; color: modelData.severity === "critical" || modelData.severity === "error" ? Theme.bad : modelData.severity === "warning" ? Theme.warn : Theme.faint; anchors.left: parent.left; anchors.leftMargin: 16; anchors.top: parent.top; anchors.topMargin: 9 }
Text { text: modelData.message || modelData.event_type || "event"; color: Theme.dim; font.pixelSize: 11; anchors.left: parent.left; anchors.leftMargin: 30; anchors.right: parent.right; anchors.rightMargin: 12; anchors.top: parent.top; anchors.topMargin: 4; elide: Text.ElideRight }
Text { text: modelData.component || "agent"; color: Theme.faint; font.pixelSize: 9; anchors.left: parent.left; anchors.leftMargin: 30; anchors.bottom: parent.bottom; anchors.bottomMargin: 3 }
}
Text { visible: app.events.length === 0; anchors.centerIn: parent; text: "No local events awaiting sync."; color: Theme.faint; font.pixelSize: 11 }
}
}
}
Item { Layout.preferredHeight: 22 }
}
}
}
Component {
id: performancePage
ScrollView {
clip: true; contentWidth: availableWidth
ColumnLayout {
width: parent.width; spacing: 14; anchors.margins: 22
Text { text: "Performance"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700; Layout.topMargin: 20 }
Text { text: "Agent-owned one-second telemetry. History is bounded to five minutes in memory."; color: Theme.dim; font.pixelSize: Theme.fontBody }
GridLayout {
Layout.fillWidth: true; columns: 4; columnSpacing: 12
MetricCard { Layout.fillWidth: true; label: "CPU"; value: app.percent(machine.cpuUsage); detail: "load " + machine.load1.toFixed(2) + " · " + machine.load5.toFixed(2) + " · " + machine.load15.toFixed(2); accent: Theme.red }
MetricCard { Layout.fillWidth: true; label: "Memory"; value: app.percent(machine.memoryPercent); detail: app.bytes(machine.memoryUsed) + " used"; accent: Theme.blue }
MetricCard { Layout.fillWidth: true; label: "Swap"; value: app.percent(machine.swapPercent); detail: "memory pressure reserve"; accent: Theme.purple }
MetricCard { Layout.fillWidth: true; label: "Disk I/O"; value: app.bytes(machine.diskRead + machine.diskWrite) + "/s"; detail: "↓ " + app.bytes(machine.diskRead) + " ↑ " + app.bytes(machine.diskWrite); accent: Theme.warn }
}
Rectangle {
Layout.fillWidth: true; Layout.preferredHeight: 280; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
Text { text: "CPU UTILIZATION"; color: Theme.faint; font.pixelSize: 10; font.letterSpacing: 1; anchors.left: parent.left; anchors.leftMargin: 16; anchors.top: parent.top; anchors.topMargin: 14 }
Text { text: app.percent(machine.cpuUsage); color: Theme.text; font.pixelSize: 30; font.weight: 650; anchors.right: parent.right; anchors.rightMargin: 16; anchors.top: parent.top; anchors.topMargin: 9 }
LineGraph { anchors.fill: parent; anchors.margins: 18; anchors.topMargin: 58; values: app.history.map(p => p.cpu_percent); ceiling: 100; lineColor: Theme.red }
}
RowLayout {
Layout.fillWidth: true; Layout.preferredHeight: 220; spacing: 12
Rectangle {
Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
Text { text: "MEMORY"; color: Theme.faint; font.pixelSize: 10; font.letterSpacing: 1; anchors.left: parent.left; anchors.leftMargin: 16; anchors.top: parent.top; anchors.topMargin: 14 }
LineGraph { anchors.fill: parent; anchors.margins: 16; anchors.topMargin: 44; values: app.history.map(p => p.memory_percent); ceiling: 100; lineColor: Theme.blue }
}
Rectangle {
Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
Text { text: "NETWORK THROUGHPUT"; color: Theme.faint; font.pixelSize: 10; font.letterSpacing: 1; anchors.left: parent.left; anchors.leftMargin: 16; anchors.top: parent.top; anchors.topMargin: 14 }
LineGraph { anchors.fill: parent; anchors.margins: 16; anchors.topMargin: 44; values: app.history.map(p => Number(p.network_receive_per_second) + Number(p.network_transmit_per_second)); ceiling: app.historyCeiling("network_receive_per_second", "network_transmit_per_second"); lineColor: Theme.cyan }
}
}
Item { Layout.preferredHeight: 22 }
}
}
}
Component {
id: processesPage
Item {
id: processRoot
property string query: search.text.toLowerCase()
property var rows: app.processes.filter(p => !query.length || String(p.name).toLowerCase().includes(query) || String(p.cmdline).toLowerCase().includes(query) || String(p.user).toLowerCase().includes(query) || String(p.pid).includes(query) || String(app.ownerLabel(p)).toLowerCase().includes(query) || String(p.unit || "").toLowerCase().includes(query))
Component.onCompleted: if (app.pendingProcessQuery.length) { search.text = app.pendingProcessQuery; app.pendingProcessQuery = "" }
ColumnLayout {
anchors.fill: parent; anchors.margins: 22; spacing: 12
RowLayout {
Layout.fillWidth: true
Column { Text { text: "Processes"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700 } Text { text: rows.length + " visible · select a process for sockets, files, namespaces, and identity"; color: Theme.dim; font.pixelSize: Theme.fontBody } }
Item { Layout.fillWidth: true }
TextField { id: search; Layout.preferredWidth: 300; placeholderText: "Find process, PID, command, owner"; color: Theme.text; placeholderTextColor: Theme.faint; background: Rectangle { color: Theme.surface; border.width: 1; border.color: search.activeFocus ? Theme.red : Theme.divider; radius: Theme.radiusSm } }
}
Rectangle {
Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
ColumnLayout {
anchors.fill: parent; spacing: 0
Rectangle {
Layout.fillWidth: true; Layout.preferredHeight: 34; color: Theme.raised
RowLayout { anchors.fill: parent; anchors.leftMargin: 14; anchors.rightMargin: 14; Text { text: "PROCESS"; color: Theme.faint; font.pixelSize: 9; Layout.fillWidth: true } Text { text: "PID"; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 70 } Text { text: "OWNER"; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 120 } Text { text: "CPU"; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 70 } Text { text: "MEMORY"; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 80 } }
}
ListView {
Layout.fillWidth: true; Layout.fillHeight: true; clip: true; model: processRoot.rows
delegate: Rectangle {
required property var modelData; width: ListView.view.width; height: 47; color: rowMouse.containsMouse ? Theme.hover : "transparent"
Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider }
RowLayout { anchors.fill: parent; anchors.leftMargin: 14; anchors.rightMargin: 14
Column { Layout.fillWidth: true; Text { text: modelData.name || "unknown"; color: Theme.text; font.pixelSize: 12; font.family: Theme.mono } Row { id: subRow; width: parent.width; spacing: 6; Text { id: ownerTag; visible: text.length > 0; text: app.ownerLabel(modelData); color: app.ownerColor(modelData); font.pixelSize: 9; font.family: Theme.mono } Text { id: ownerDot; visible: ownerTag.visible; text: "·"; color: Theme.faint; font.pixelSize: 9 } Text { width: subRow.width - (ownerTag.visible ? ownerTag.width + ownerDot.width + subRow.spacing * 2 : 0); text: modelData.cmdline || modelData.path || ""; color: Theme.faint; font.pixelSize: 9; elide: Text.ElideRight } } }
Text { text: String(modelData.pid); color: Theme.dim; font.pixelSize: 11; font.family: Theme.mono; Layout.preferredWidth: 70 }
Text { text: modelData.user || String(modelData.uid); color: Theme.dim; font.pixelSize: 11; Layout.preferredWidth: 120; elide: Text.ElideRight }
Text { text: Number(modelData.cpu_percent || 0).toFixed(1) + "%"; color: Theme.pressure(Number(modelData.cpu_percent || 0)); font.pixelSize: 11; Layout.preferredWidth: 70 }
Text { text: app.bytes(modelData.rss_bytes); color: Theme.dim; font.pixelSize: 11; Layout.preferredWidth: 80 }
}
MouseArea { id: rowMouse; anchors.fill: parent; hoverEnabled: true; onClicked: machine.loadProcess(Number(modelData.pid)) }
}
}
}
}
}
}
}
Component {
id: networkPage
Item {
id: networkRoot
property string query: networkSearch.text.toLowerCase()
property var rows: app.connections.filter(c => !query.length || String(c.process).toLowerCase().includes(query) || String(c.local_addr).includes(query) || String(c.remote_addr).includes(query) || String(c.local_port).includes(query))
ColumnLayout {
anchors.fill: parent; anchors.margins: 22; spacing: 12
RowLayout { Layout.fillWidth: true; Column { Text { text: "Network"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700 } Text { text: "Interfaces, throughput, listeners, and live socket ownership."; color: Theme.dim; font.pixelSize: Theme.fontBody } } Item { Layout.fillWidth: true } TextField { id: networkSearch; Layout.preferredWidth: 280; placeholderText: "Find process, address, port"; color: Theme.text; placeholderTextColor: Theme.faint; background: Rectangle { color: Theme.surface; border.width: 1; border.color: networkSearch.activeFocus ? Theme.red : Theme.divider; radius: Theme.radiusSm } } }
RowLayout { Layout.fillWidth: true; MetricCard { Layout.fillWidth: true; label: "Receive"; value: app.bytes(machine.networkReceive) + "/s"; detail: "all non-loopback interfaces"; accent: Theme.cyan } MetricCard { Layout.fillWidth: true; label: "Transmit"; value: app.bytes(machine.networkTransmit) + "/s"; detail: "all non-loopback interfaces"; accent: Theme.purple } MetricCard { Layout.fillWidth: true; label: "Sockets"; value: String(rows.length); detail: "TCP and UDP · process-correlated"; accent: Theme.blue } }
Rectangle {
Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
ColumnLayout { anchors.fill: parent; spacing: 0
Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 34; color: Theme.raised; RowLayout { anchors.fill: parent; anchors.leftMargin: 14; anchors.rightMargin: 14; Text { text: "PROCESS"; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 180 } Text { text: "PROTOCOL"; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 80 } Text { text: "LOCAL"; color: Theme.faint; font.pixelSize: 9; Layout.fillWidth: true } Text { text: "REMOTE"; color: Theme.faint; font.pixelSize: 9; Layout.fillWidth: true } Text { text: "STATE"; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 100 } } }
ListView { Layout.fillWidth: true; Layout.fillHeight: true; clip: true; model: networkRoot.rows
delegate: Rectangle { required property var modelData; width: ListView.view.width; height: 38; color: "transparent"; Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider } RowLayout { anchors.fill: parent; anchors.leftMargin: 14; anchors.rightMargin: 14; Text { text: (modelData.process || "unknown") + (modelData.pid ? " · " + modelData.pid : ""); color: Theme.text; font.pixelSize: 11; font.family: Theme.mono; Layout.preferredWidth: 180; elide: Text.ElideRight } Text { text: modelData.protocol + " " + modelData.family; color: Theme.dim; font.pixelSize: 10; Layout.preferredWidth: 80 } Text { text: app.endpoint(modelData.local_addr, modelData.local_port); color: Theme.dim; font.pixelSize: 10; font.family: Theme.mono; Layout.fillWidth: true; elide: Text.ElideRight } Text { text: modelData.remote_port ? app.endpoint(modelData.remote_addr, modelData.remote_port) : "—"; color: Theme.dim; font.pixelSize: 10; font.family: Theme.mono; Layout.fillWidth: true; elide: Text.ElideRight } Text { text: modelData.state; color: modelData.state === "LISTEN" ? Theme.warn : modelData.state === "ESTABLISHED" ? Theme.good : Theme.faint; font.pixelSize: 10; Layout.preferredWidth: 100 } } MouseArea { anchors.fill: parent; hoverEnabled: true; onEntered: parent.color = Theme.hover; onExited: parent.color = "transparent"; onClicked: if (modelData.pid) machine.loadProcess(Number(modelData.pid)) } }
}
}
}
}
}
}
Component {
id: storagePage
ScrollView {
clip: true; contentWidth: availableWidth
ColumnLayout {
width: parent.width; anchors.margins: 22; spacing: 14
Text { text: "Storage"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700; Layout.topMargin: 20 }
Text { text: "Filesystems and physical-device throughput observed by the Agent."; color: Theme.dim; font.pixelSize: Theme.fontBody }
RowLayout { Layout.fillWidth: true; MetricCard { Layout.fillWidth: true; label: "Read"; value: app.bytes(machine.diskRead) + "/s"; detail: "physical block devices"; accent: Theme.blue } MetricCard { Layout.fillWidth: true; label: "Write"; value: app.bytes(machine.diskWrite) + "/s"; detail: "physical block devices"; accent: Theme.warn } }
Repeater {
model: app.system.disk_info || []
Rectangle {
required property var modelData; Layout.fillWidth: true; Layout.preferredHeight: 88; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
RowLayout { anchors.fill: parent; anchors.margins: 15
Column { Layout.fillWidth: true; Text { text: modelData.mountpoint + (modelData.is_root ? " · ROOT" : ""); color: Theme.text; font.pixelSize: 13; font.family: Theme.mono; font.weight: 600 } Text { text: modelData.filesystem + " · " + app.bytes(modelData.used) + " used · " + app.bytes(modelData.available) + " available"; color: Theme.dim; font.pixelSize: 11 } }
Text { text: app.percent(modelData.used_percent); color: Theme.pressure(modelData.used_percent); font.pixelSize: 22; font.weight: 650 }
}
Rectangle { anchors.left: parent.left; anchors.right: parent.right; anchors.bottom: parent.bottom; anchors.margins: 15; height: 4; radius: 2; color: Theme.divider; Rectangle { width: parent.width * Math.min(1, Number(modelData.used_percent) / 100); height: parent.height; radius: 2; color: Theme.pressure(modelData.used_percent) } }
}
}
Item { Layout.preferredHeight: 22 }
}
}
}
Component {
id: containersPage
Item {
ColumnLayout { anchors.fill: parent; anchors.margins: 22; spacing: 12
Text { text: "Containers"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700 }
Text { text: "Docker containers and Compose/stack identity from the Agent's existing container model."; color: Theme.dim; font.pixelSize: Theme.fontBody }
RowLayout { Layout.fillWidth: true; MetricCard { Layout.fillWidth: true; label: "Containers"; value: machine.containerRunning + " / " + machine.containerCount; detail: "running / total"; accent: Theme.blue } MetricCard { Layout.fillWidth: true; label: "Health"; value: machine.containerUnhealthy ? machine.containerUnhealthy + " unhealthy" : "Healthy"; detail: "reported Docker health checks"; accent: machine.containerUnhealthy ? Theme.bad : Theme.good } }
Rectangle { Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
ListView { anchors.fill: parent; clip: true; model: app.containers
delegate: Rectangle { required property var modelData; width: ListView.view.width; height: 58; color: "transparent"; Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider } RowLayout { anchors.fill: parent; anchors.margins: 13; Rectangle { width: 8; height: 8; radius: 4; color: modelData.health === "unhealthy" ? Theme.bad : modelData.state === "running" ? Theme.good : Theme.faint } Column { Layout.fillWidth: true; Text { text: modelData.name || modelData.container_id; color: Theme.text; font.pixelSize: 12; font.family: Theme.mono } Text { text: modelData.image + (modelData.stack_name ? " · " + modelData.stack_name : ""); color: Theme.dim; font.pixelSize: 10 } } Text { text: app.processCountLabel(app.containerProcessCount(modelData.container_id)); color: Theme.cyan; font.pixelSize: 10; font.family: Theme.mono; Layout.preferredWidth: 100 } Text { text: modelData.ports || "no published ports"; color: Theme.faint; font.pixelSize: 10; font.family: Theme.mono; Layout.preferredWidth: 260; elide: Text.ElideRight } Text { text: (modelData.health || modelData.state || "unknown").toUpperCase(); color: modelData.health === "unhealthy" ? Theme.bad : modelData.state === "running" ? Theme.good : Theme.faint; font.pixelSize: 9; font.weight: 700; Layout.preferredWidth: 90 } } MouseArea { anchors.fill: parent; hoverEnabled: true; onEntered: parent.color = Theme.hover; onExited: parent.color = "transparent"; onClicked: { app.pendingProcessQuery = String(modelData.name || modelData.container_id || ""); app.page = "processes" } } }
Text { visible: app.containers.length === 0; anchors.centerIn: parent; text: "Docker unavailable or no containers are present."; color: Theme.faint; font.pixelSize: 12 }
}
}
}
}
}
Component {
id: servicesPage
Item {
id: servicesRoot
property string query: serviceSearch.text.toLowerCase()
property var rows: app.services.filter(s => !query.length || String(s.name).toLowerCase().includes(query) || String(s.description).toLowerCase().includes(query))
ColumnLayout { anchors.fill: parent; anchors.margins: 22; spacing: 12
RowLayout { Layout.fillWidth: true; Column { Text { text: "Services"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700 } Text { text: "Cross-platform service domain, currently backed by systemd observation on Linux."; color: Theme.dim; font.pixelSize: Theme.fontBody } } Item { Layout.fillWidth: true } TextField { id: serviceSearch; Layout.preferredWidth: 280; placeholderText: "Find service"; color: Theme.text; placeholderTextColor: Theme.faint; background: Rectangle { color: Theme.surface; border.width: 1; border.color: serviceSearch.activeFocus ? Theme.red : Theme.divider; radius: Theme.radiusSm } } }
RowLayout { Layout.fillWidth: true; MetricCard { Layout.fillWidth: true; label: "Running"; value: String(machine.serviceRunning); detail: "active services"; accent: Theme.good } MetricCard { Layout.fillWidth: true; label: "Failed"; value: String(machine.serviceFailed); detail: "requires operator attention"; accent: machine.serviceFailed ? Theme.bad : Theme.good } }
Rectangle { Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
ListView { anchors.fill: parent; clip: true; model: servicesRoot.rows
delegate: Rectangle { required property var modelData; width: ListView.view.width; height: 48; color: "transparent"; Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider } RowLayout { anchors.fill: parent; anchors.margins: 13; Rectangle { width: 7; height: 7; radius: 4; color: modelData.active_state === "failed" ? Theme.bad : modelData.active_state === "active" ? Theme.good : Theme.faint } Column { Layout.fillWidth: true; Text { text: modelData.name; color: Theme.text; font.pixelSize: 12; font.family: Theme.mono } Text { text: modelData.description || ""; color: Theme.faint; font.pixelSize: 10; elide: Text.ElideRight } } Text { text: app.processCountLabel(app.unitProcessCount(modelData.name)); color: Theme.blue; font.pixelSize: 10; font.family: Theme.mono; Layout.preferredWidth: 100 } Text { text: (modelData.active_state + " · " + modelData.sub_state).toUpperCase(); color: modelData.active_state === "failed" ? Theme.bad : modelData.active_state === "active" ? Theme.good : Theme.faint; font.pixelSize: 9; font.weight: 700; Layout.preferredWidth: 130 } } MouseArea { anchors.fill: parent; hoverEnabled: true; onEntered: parent.color = Theme.hover; onExited: parent.color = "transparent"; onClicked: { app.pendingProcessQuery = String(modelData.name || "") + ".service"; app.page = "processes" } } }
}
}
}
}
}
Component {
id: softwarePage
Item {
id: softwareRoot
property string query: softwareSearch.text.toLowerCase()
property var rows: app.software.filter(p => !query.length
|| String(p.name).toLowerCase().includes(query)
|| String(p.description).toLowerCase().includes(query)
|| String(p.package_type).toLowerCase().includes(query))
ColumnLayout {
anchors.fill: parent; anchors.margins: 22; spacing: 12
RowLayout {
Layout.fillWidth: true
Column {
Text { text: "Software"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700 }
Text { text: "Everything the machine's package managers say is installed. Select one to follow its ownership and dependency edges."; color: Theme.dim; font.pixelSize: Theme.fontBody }
}
Item { Layout.fillWidth: true }
TextField {
id: softwareSearch
Layout.preferredWidth: 320
placeholderText: "Find installed software"
color: Theme.text; placeholderTextColor: Theme.faint
background: Rectangle { color: Theme.surface; border.width: 1; border.color: softwareSearch.activeFocus ? Theme.red : Theme.divider; radius: Theme.radiusSm }
}
Button {
text: machine.softwareLoading ? "Reading…" : "Refresh"
enabled: !machine.softwareLoading
onClicked: machine.refreshSoftware()
}
}
RowLayout {
Layout.fillWidth: true
MetricCard { Layout.fillWidth: true; label: "Installed"; value: String(machine.softwareCount); detail: "known local packages"; accent: Theme.blue }
MetricCard { Layout.fillWidth: true; label: "Explicit"; value: String(machine.softwareExplicitCount); detail: "chosen by an operator"; accent: Theme.cyan }
MetricCard { Layout.fillWidth: true; label: "Dependencies"; value: String(machine.softwareDependencyCount); detail: "pulled into closures"; accent: Theme.purple }
MetricCard { Layout.fillWidth: true; label: "Foreign"; value: String(machine.softwareForeignCount); detail: "outside known repositories"; accent: machine.softwareForeignCount > 0 ? Theme.warn : Theme.good }
}
Rectangle {
Layout.fillWidth: true; Layout.fillHeight: true
color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
ListView {
anchors.fill: parent; clip: true; model: softwareRoot.rows
delegate: Rectangle {
required property var modelData
width: ListView.view.width; height: 58
color: packageMouse.containsMouse ? Theme.hover : "transparent"
Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider }
RowLayout {
anchors.fill: parent; anchors.margins: 13
Column {
Layout.fillWidth: true
Text { text: modelData.name || modelData.identity; color: Theme.text; font.pixelSize: 12; font.family: Theme.mono; font.weight: 600 }
Text { text: modelData.description || "No description reported"; color: Theme.dim; font.pixelSize: 10; elide: Text.ElideRight; width: parent.width }
}
Text { text: modelData.version || "unknown"; color: Theme.text; font.pixelSize: 10; font.family: Theme.mono; Layout.preferredWidth: 180; elide: Text.ElideRight }
Text { text: String(modelData.install_reason || "unknown").toUpperCase(); color: modelData.install_reason === "explicit" ? Theme.cyan : Theme.faint; font.pixelSize: 9; font.weight: 700; Layout.preferredWidth: 100 }
Text { text: String(modelData.package_type || "unknown").toUpperCase(); color: Theme.faint; font.pixelSize: 9; font.weight: 700; Layout.preferredWidth: 72 }
Text { text: modelData.origin === "foreign" ? "FOREIGN" : ""; color: Theme.warn; font.pixelSize: 9; font.weight: 700; Layout.preferredWidth: 64 }
}
MouseArea {
id: packageMouse; anchors.fill: parent; hoverEnabled: true
onClicked: machine.loadSoftware(modelData.package_type || "", modelData.identity || modelData.name || "")
}
}
Text { visible: app.software.length === 0 && !machine.softwareLoading; anchors.centerIn: parent; text: "The Agent reported no installed package inventory."; color: Theme.faint; font.pixelSize: 12 }
}
}
}
}
}
Component {
id: updatesPage
Item {
ColumnLayout { anchors.fill: parent; anchors.margins: 22; spacing: 12
RowLayout { Layout.fillWidth: true; Column { Text { text: "Updates"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700 } Text { text: "Available changes, vulnerability evidence, and the RedFlag mutation path."; color: Theme.dim; font.pixelSize: Theme.fontBody } } Item { Layout.fillWidth: true } Button { text: machine.overviewLoading ? "Scanning…" : "Scan now"; enabled: !machine.overviewLoading; onClicked: machine.triggerScan(); background: Rectangle { color: parent.pressed ? Qt.darker(Theme.red, 1.15) : Theme.red; radius: Theme.radiusSm } contentItem: Text { text: parent.text; color: "white"; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; font.pixelSize: 11; font.weight: 650 } } }
Rectangle { visible: machine.operationMessage.length > 0; Layout.fillWidth: true; Layout.preferredHeight: 36; color: Theme.redDim; radius: Theme.radiusSm; Text { anchors.centerIn: parent; text: machine.operationMessage; color: Theme.dim; font.pixelSize: 11 } }
Rectangle { Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
ListView { anchors.fill: parent; clip: true; model: app.updates
delegate: Rectangle { required property var modelData; width: ListView.view.width; height: 68; color: "transparent"; Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider } RowLayout { anchors.fill: parent; anchors.margins: 14; Rectangle { width: 8; height: 8; radius: 4; color: modelData.severity === "critical" ? Theme.bad : modelData.severity === "high" || modelData.severity === "important" ? Theme.warn : Theme.faint } Column { Layout.fillWidth: true; Text { text: modelData.package_name; color: Theme.text; font.pixelSize: 13; font.family: Theme.mono; font.weight: 600 } Text { text: (modelData.current_version || "?") + " → " + (modelData.available_version || "?") + (modelData.cve_list && modelData.cve_list.length ? " · " + modelData.cve_list.length + " advisories" : ""); color: Theme.dim; font.pixelSize: 10; font.family: Theme.mono } } Text { text: String(modelData.package_type || "unknown").toUpperCase(); color: Theme.faint; font.pixelSize: 9; font.weight: 700; Layout.preferredWidth: 90 } Column { Layout.preferredWidth: 150; Text { text: "DISCOVERED"; color: Theme.warn; font.pixelSize: 9; font.weight: 700; anchors.right: parent.right } Text { text: app.locallyApprovable(modelData) && machine.enrollment !== "fleet enrolled" ? "authorize here" : "awaiting authorization"; color: Theme.faint; font.pixelSize: 9; anchors.right: parent.right } } } MouseArea { anchors.fill: parent; hoverEnabled: true; onEntered: parent.color = Theme.hover; onExited: parent.color = "transparent"; onClicked: { app.selectedUpdate = modelData; machine.approvalJson = "{}"; updateDialog.open() } } }
Text { visible: app.updates.length === 0; anchors.centerIn: parent; text: "No pending updates."; color: Theme.good; font.pixelSize: 12 }
}
}
Text { text: "Package execution remains Agent → signed envelope → helper. This glass does not own a package-manager escape hatch."; color: Theme.faint; font.pixelSize: 10; Layout.alignment: Qt.AlignHCenter }
}
}
}
Component {
id: securityPage
ScrollView {
clip: true; contentWidth: availableWidth
ColumnLayout { width: parent.width; anchors.margins: 22; spacing: 14
Text { text: "Security & supply chain"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700; Layout.topMargin: 20 }
Text { text: "Evidence and enforcement state. Unknown stays unknown; no invented green checkmarks."; color: Theme.dim; font.pixelSize: Theme.fontBody }
GridLayout {
Layout.fillWidth: true; columns: 2; columnSpacing: 12; rowSpacing: 12
Repeater {
model: [
{ label: "Command signing", value: app.security.command_signing_enabled ? "Enabled" : "Disabled", detail: app.security.command_enforcement || "unknown", ok: app.security.command_signing_enabled && app.security.command_enforcement === "strict" },
{ label: "TLS verification", value: app.security.tls_verification ? "Enforced" : "Bypassed", detail: "Agent to fleet server", ok: app.security.tls_verification },
{ label: "Security logging", value: app.security.security_logging ? "Enabled" : "Disabled", detail: "local security event channel", ok: app.security.security_logging },
{ label: "Kernel enforcement", value: app.security.kernel_enforcement ? "Configured" : "Not configured", detail: app.security.kernel_fail_closed ? "fail closed" : "fail open / unknown", ok: app.security.kernel_enforcement && app.security.kernel_fail_closed },
{ label: "Critical updates", value: String(machine.criticalCount), detail: "reported package findings", ok: machine.criticalCount === 0 },
{ label: "Agent mode", value: app.security.degraded_mode ? "Degraded" : "Normal", detail: machine.enrollment, ok: !app.security.degraded_mode },
{ label: "Capability tokens", value: String((app.security.capabilities || {}).pending_count || 0) + " pending", detail: ((app.security.capabilities || {}).last_failed_count || 0) + " failed · " + ((app.security.capabilities || {}).last_processed_count || 0) + " processed", ok: ((app.security.capabilities || {}).last_failed_count || 0) === 0 },
{ label: "Privileged processes", value: app.processes.length ? String(app.sharpProcessCount()) : "unscanned", detail: app.processes.length ? "hold a capability that can rewrite this machine" : "open Processes to scan", ok: app.processes.length > 0 && app.sharpProcessCount() === 0 }
]
Rectangle { required property var modelData; Layout.fillWidth: true; Layout.preferredHeight: 94; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider; Rectangle { width: 4; height: parent.height - 26; radius: 2; color: modelData.ok ? Theme.good : Theme.warn; anchors.left: parent.left; anchors.leftMargin: 14; anchors.verticalCenter: parent.verticalCenter } Column { anchors.left: parent.left; anchors.leftMargin: 31; anchors.verticalCenter: parent.verticalCenter; Text { text: modelData.label.toUpperCase(); color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1 } Text { text: modelData.value; color: modelData.ok ? Theme.good : Theme.warn; font.pixelSize: 19; font.weight: 650 } Text { text: modelData.detail; color: Theme.dim; font.pixelSize: 10 } } }
}
}
Rectangle { Layout.fillWidth: true; Layout.preferredHeight: 100; color: Theme.redDim; radius: Theme.radius; border.width: 1; border.color: Theme.tint(Theme.red, 0.45); Column { anchors.fill: parent; anchors.margins: 15; spacing: 5; Text { text: "AUTHORITY BOUNDARY"; color: Theme.red; font.pixelSize: 10; font.weight: 750; font.letterSpacing: 1 } Text { width: parent.width; text: "QML observes RedFlag state and expresses operator intent. The Agent resolves policy and authorization. The privileged helper verifies a bounded mutation envelope before any platform backend runs."; color: Theme.text; font.pixelSize: 12; wrapMode: Text.Wrap } Text { text: "Override uses the same machinery and must leave actor, reason, target, bypass, and aftermath in history."; color: Theme.dim; font.pixelSize: 10 } } }
Item { Layout.preferredHeight: 22 }
}
}
}
Component {
id: historyPage
Item {
ColumnLayout { anchors.fill: parent; anchors.margins: 22; spacing: 12
Text { text: "History"; color: Theme.text; font.pixelSize: Theme.fontTitle; font.weight: 700 }
Text { text: "Local operational events awaiting fleet synchronization. Mutation receipts join this same timeline."; color: Theme.dim; font.pixelSize: Theme.fontBody }
Rectangle { Layout.fillWidth: true; Layout.fillHeight: true; color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider
ListView { anchors.fill: parent; clip: true; model: app.events
delegate: Rectangle { required property var modelData; width: ListView.view.width; height: 64; color: "transparent"; Rectangle { anchors.bottom: parent.bottom; width: parent.width; height: 1; color: Theme.divider } RowLayout { anchors.fill: parent; anchors.margins: 14; Rectangle { width: 8; height: 8; radius: 4; color: modelData.severity === "critical" || modelData.severity === "error" ? Theme.bad : modelData.severity === "warning" ? Theme.warn : Theme.faint } Column { Layout.fillWidth: true; Text { text: modelData.message || modelData.event_type; color: Theme.text; font.pixelSize: 12; elide: Text.ElideRight; width: parent.width } Text { text: (modelData.component || "agent") + " · " + (modelData.event_subtype || "event"); color: Theme.dim; font.pixelSize: 10 } } Text { text: modelData.created_at ? new Date(modelData.created_at).toLocaleString() : ""; color: Theme.faint; font.pixelSize: 9; Layout.preferredWidth: 180; horizontalAlignment: Text.AlignRight } } }
Text { visible: app.events.length === 0; anchors.centerIn: parent; text: "No local events are waiting to synchronize."; color: Theme.faint; font.pixelSize: 12 }
}
}
}
}
}
Dialog {
id: processDialog
width: Math.min(760, app.width - 80)
height: Math.min(650, app.height - 80)
anchors.centerIn: parent
modal: true
title: app.processDetail.name ? app.processDetail.name + " · PID " + app.processDetail.pid : "Process"
background: Rectangle { color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider }
header: Rectangle { width: parent.width; height: 54; color: Theme.raised; Text { anchors.left: parent.left; anchors.leftMargin: 16; anchors.verticalCenter: parent.verticalCenter; text: processDialog.title; color: Theme.text; font.pixelSize: 16; font.weight: 650 } }
contentItem: ScrollView {
clip: true
ColumnLayout {
width: processDialog.availableWidth; spacing: 8
Text { text: app.processDetail.error || app.processDetail.path || "Path unavailable"; color: app.processDetail.error ? Theme.bad : Theme.dim; font.pixelSize: 11; font.family: Theme.mono; wrapMode: Text.Wrap; Layout.fillWidth: true }
Text { text: app.processDetail.cmdline || ""; color: Theme.text; font.pixelSize: 11; font.family: Theme.mono; wrapMode: Text.Wrap; Layout.fillWidth: true }
Text { visible: text.length > 0; text: app.processDetail.cgroup || ""; color: Theme.faint; font.pixelSize: 10; font.family: Theme.mono; wrapMode: Text.Wrap; Layout.fillWidth: true }
GridLayout { columns: 4; Layout.fillWidth: true; columnSpacing: 12; rowSpacing: 8
Repeater { model: [
{l:"Owner",v:app.processDetail.user || app.processDetail.uid || "—"}, {l:"State",v:app.processDetail.state || "—"},
{l:"CPU",v:Number(app.processDetail.cpu_percent || 0).toFixed(1)+"%"}, {l:"Memory",v:app.bytes(app.processDetail.rss_bytes)},
{l:"Threads",v:app.processDetail.threads || 0}, {l:"Parent PID",v:app.processDetail.parent_pid || 0},
{l:"Read",v:app.bytes(app.processDetail.disk_bytes_read)}, {l:"Written",v:app.bytes(app.processDetail.disk_bytes_written)},
{l:"Belongs to",v:app.ownerLabel(app.processDetail) || "unattributed"}, {l:"Package",v:app.processDetail.package_name ? app.processDetail.package_name + " · " + String(app.processDetail.package_manager || "").toUpperCase() : "unattributed"},
{l:"Elevation",v:app.processDetail.elevation_status || "normal"}
]; Rectangle { required property var modelData; Layout.fillWidth: true; Layout.preferredHeight: 62; color: Theme.raised; radius: Theme.radiusSm; Column { anchors.centerIn: parent; Text { anchors.horizontalCenter: parent.horizontalCenter; text: modelData.l.toUpperCase(); color: Theme.faint; font.pixelSize: 8 } Text { anchors.horizontalCenter: parent.horizontalCenter; text: modelData.v; color: Theme.text; font.pixelSize: 12; font.family: Theme.mono } } } }
}
Text { text: "LISTENERS & CONNECTIONS"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 8 }
Repeater { model: app.processDetail.open_sockets || []; Rectangle { required property var modelData; Layout.fillWidth: true; Layout.preferredHeight: 32; color: Theme.raised; radius: Theme.radiusSm; RowLayout { anchors.fill: parent; anchors.margins: 8; Text { text: modelData.protocol + " " + modelData.family; color: Theme.dim; font.pixelSize: 9; Layout.preferredWidth: 70 } Text { text: app.endpoint(modelData.local_addr, modelData.local_port); color: Theme.text; font.pixelSize: 10; font.family: Theme.mono; Layout.fillWidth: true } Text { text: modelData.remote_port ? app.endpoint(modelData.remote_addr, modelData.remote_port) : modelData.path || "—"; color: Theme.dim; font.pixelSize: 10; font.family: Theme.mono; Layout.fillWidth: true } Text { text: modelData.state || ""; color: Theme.faint; font.pixelSize: 9 } } } }
Text { text: "EFFECTIVE CAPABILITIES · " + ((app.processDetail.capabilities || []).length || "none"); color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 8 }
Flow { Layout.fillWidth: true; spacing: 6; Repeater { model: app.processDetail.capabilities || []; Rectangle { required property var modelData; width: capText.implicitWidth + 14; height: 25; radius: 12; color: Theme.raised; Text { id: capText; anchors.centerIn: parent; text: modelData; color: app.capabilityColor(modelData); font.pixelSize: 9; font.family: Theme.mono } } } }
Text { text: "NAMESPACES"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 8 }
Flow { Layout.fillWidth: true; spacing: 6; Repeater { model: app.processDetail.namespaces || []; Rectangle { required property var modelData; width: nsText.implicitWidth + 14; height: 25; radius: 12; color: Theme.raised; Text { id: nsText; anchors.centerIn: parent; text: modelData.type + ":" + modelData.inode; color: Theme.dim; font.pixelSize: 9; font.family: Theme.mono } } } }
}
}
footer: DialogButtonBox { standardButtons: DialogButtonBox.Close; onRejected: processDialog.close() }
}
Dialog {
id: softwareDialog
width: Math.min(840, app.width - 80)
height: Math.min(700, app.height - 80)
anchors.centerIn: parent
modal: true
title: app.softwareDetail.name || app.softwareDetail.identity || "Installed software"
background: Rectangle { color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider }
header: Rectangle {
width: parent.width; height: 68; color: Theme.raised
RowLayout {
anchors.fill: parent; anchors.leftMargin: 17; anchors.rightMargin: 17
Column {
Layout.fillWidth: true
Text { text: softwareDialog.title; color: Theme.text; font.pixelSize: 17; font.weight: 650; font.family: Theme.mono }
Text { text: (app.softwareDetail.version || "unknown") + (app.softwareDetail.architecture ? " · " + app.softwareDetail.architecture : ""); color: Theme.dim; font.pixelSize: 10; font.family: Theme.mono }
}
Text { text: String(app.softwareDetail.package_type || "unknown").toUpperCase(); color: Theme.cyan; font.pixelSize: 10; font.weight: 700; font.letterSpacing: 1 }
}
}
contentItem: ScrollView {
clip: true; contentWidth: availableWidth
ColumnLayout {
width: softwareDialog.availableWidth; spacing: 10
Text { visible: app.softwareDetail.error !== undefined; text: app.softwareDetail.error || "Software detail unavailable"; color: Theme.bad; font.pixelSize: 11; font.family: Theme.mono; wrapMode: Text.Wrap; Layout.fillWidth: true }
Text { visible: app.softwareDetail.error === undefined; text: app.softwareDetail.description || "No description reported by the package manager."; color: Theme.text; font.pixelSize: 12; wrapMode: Text.Wrap; Layout.fillWidth: true }
GridLayout {
visible: app.softwareDetail.error === undefined
columns: 4; Layout.fillWidth: true; columnSpacing: 10; rowSpacing: 8
Repeater {
model: [
{ l: "Reason", v: app.softwareDetail.install_reason || "unknown" },
{ l: "Origin", v: app.softwareDetail.origin || "unknown" },
{ l: "Installed size", v: app.bytes(app.softwareDetail.installed_size_bytes) },
{ l: "Files", v: String(app.softwareDetail.file_count || 0) + (app.softwareDetail.files_truncated ? "+" : "") },
{ l: "Packager", v: app.softwareDetail.packager || "unknown" },
{ l: "Build date", v: app.softwareDetail.build_date || "unknown" },
{ l: "Installed", v: app.softwareDetail.installed_at || "unknown" },
{ l: "Identity", v: app.softwareDetail.identity || "unknown" }
]
Rectangle {
required property var modelData
Layout.fillWidth: true; Layout.preferredHeight: 62; color: Theme.raised; radius: Theme.radiusSm
Column {
anchors.centerIn: parent; width: parent.width - 14
Text { anchors.horizontalCenter: parent.horizontalCenter; text: modelData.l.toUpperCase(); color: Theme.faint; font.pixelSize: 8 }
Text { width: parent.width; horizontalAlignment: Text.AlignHCenter; text: modelData.v; color: Theme.text; font.pixelSize: 10; font.family: Theme.mono; elide: Text.ElideMiddle }
}
}
}
}
Text { visible: (app.softwareDetail.depends_on || []).length > 0; text: "DEPENDENCY CLOSURE EDGES"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 5 }
Flow {
visible: (app.softwareDetail.depends_on || []).length > 0
Layout.fillWidth: true; spacing: 6
Repeater { model: app.softwareDetail.depends_on || []; Rectangle { required property var modelData; width: dependencyText.implicitWidth + 14; height: 25; radius: 12; color: Theme.raised; Text { id: dependencyText; anchors.centerIn: parent; text: modelData; color: Theme.cyan; font.pixelSize: 9; font.family: Theme.mono } } }
}
Text { visible: (app.softwareDetail.required_by || []).length > 0; text: "REQUIRED BY"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 5 }
Flow {
visible: (app.softwareDetail.required_by || []).length > 0
Layout.fillWidth: true; spacing: 6
Repeater { model: app.softwareDetail.required_by || []; Rectangle { required property var modelData; width: requiredText.implicitWidth + 14; height: 25; radius: 12; color: Theme.raised; Text { id: requiredText; anchors.centerIn: parent; text: modelData; color: Theme.purple; font.pixelSize: 9; font.family: Theme.mono } } }
}
Text { visible: (app.softwareDetail.provides || []).length > 0; text: "PROVIDES"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 5 }
Text { visible: (app.softwareDetail.provides || []).length > 0; Layout.fillWidth: true; text: (app.softwareDetail.provides || []).join(" · "); color: Theme.dim; font.pixelSize: 10; font.family: Theme.mono; wrapMode: Text.Wrap }
Text { visible: (app.softwareDetail.files || []).length > 0; text: "FILES"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 5 }
Repeater {
model: (app.softwareDetail.files || []).slice(0, 80)
Text { required property var modelData; Layout.fillWidth: true; text: modelData; color: Theme.dim; font.pixelSize: 9; font.family: Theme.mono; elide: Text.ElideMiddle }
}
Text { visible: (app.softwareDetail.file_count || 0) > 80; text: "Showing 80 of " + app.softwareDetail.file_count + " paths"; color: Theme.faint; font.pixelSize: 9; font.family: Theme.mono }
}
}
footer: DialogButtonBox { standardButtons: DialogButtonBox.Close; onRejected: softwareDialog.close() }
}
Dialog {
id: updateDialog
width: Math.min(820, app.width - 80)
height: Math.min(700, app.height - 80)
anchors.centerIn: parent
modal: true
background: Rectangle { color: Theme.surface; radius: Theme.radius; border.width: 1; border.color: Theme.divider }
header: Rectangle {
width: parent.width; height: 62; color: Theme.raised
Column {
anchors.left: parent.left; anchors.leftMargin: 16; anchors.verticalCenter: parent.verticalCenter; spacing: 2
Text { text: app.selectedUpdate.package_name || "Update"; color: Theme.text; font.pixelSize: 16; font.weight: 650; font.family: Theme.mono }
Text {
text: (app.selectedUpdate.current_version || "?") + " → " + (app.selectedUpdate.available_version || "?")
+ " · " + String(app.selectedUpdate.package_type || "").toUpperCase()
color: Theme.dim; font.pixelSize: 11; font.family: Theme.mono
}
}
Text {
anchors.right: parent.right; anchors.rightMargin: 16; anchors.verticalCenter: parent.verticalCenter
text: String(app.selectedUpdate.severity || "unknown").toUpperCase()
color: app.selectedUpdate.severity === "critical" ? Theme.bad : app.selectedUpdate.severity === "high" || app.selectedUpdate.severity === "important" ? Theme.warn : Theme.faint
font.pixelSize: 9; font.weight: 700; font.letterSpacing: 1
}
}
contentItem: ScrollView {
clip: true
ColumnLayout {
width: updateDialog.availableWidth; spacing: 10
Text { text: "MUTATION PIPELINE"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1 }
RowLayout {
Layout.fillWidth: true; spacing: 0
Repeater {
model: [
{ label: "Discovered", detail: app.selectedUpdate.repository_source || "reported by the scanner" },
{ label: "Checked", detail: "closure resolved · hashes pinned · OSV" },
{ label: "Authorized", detail: "capability minted for this host" },
{ label: "Installed", detail: "helper verified and executed" }
]
Item {
required property var modelData
required property int index
Layout.fillWidth: true
Layout.preferredHeight: 66
Rectangle {
id: dot
width: 15; height: 15; radius: 8
anchors.horizontalCenter: parent.horizontalCenter; anchors.top: parent.top; anchors.topMargin: 4
color: app.stageColor(app.stageState(index))
opacity: app.stageState(index) === "idle" ? 0.35 : 1
}
Rectangle {
visible: index < 3
height: 2
anchors.left: dot.right; anchors.right: parent.right; anchors.verticalCenter: dot.verticalCenter
color: app.stageColor(app.stageState(index + 1))
opacity: 0.35
}
Text {
anchors.horizontalCenter: parent.horizontalCenter; anchors.top: dot.bottom; anchors.topMargin: 7
text: modelData.label; color: Theme.text; font.pixelSize: 11; font.weight: 600
}
Text {
anchors.horizontalCenter: parent.horizontalCenter; anchors.bottom: parent.bottom
width: parent.width - 12; horizontalAlignment: Text.AlignHCenter
text: modelData.detail; color: Theme.faint; font.pixelSize: 8; elide: Text.ElideRight
}
}
}
}
Text { visible: (app.selectedUpdate.cve_list || []).length > 0; text: "ADVISORIES"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 6 }
Flow {
Layout.fillWidth: true; spacing: 6
Repeater {
model: app.selectedUpdate.cve_list || []
Rectangle {
required property var modelData
width: cveText.implicitWidth + 14; height: 25; radius: 12; color: Theme.redDim
Text { id: cveText; anchors.centerIn: parent; text: modelData; color: Theme.bad; font.pixelSize: 9; font.family: Theme.mono }
}
}
}
Text { visible: app.approval.request_id !== undefined || app.approval.error !== undefined; text: "GATE EVIDENCE"; color: Theme.faint; font.pixelSize: 9; font.letterSpacing: 1; Layout.topMargin: 6 }
GridLayout {
visible: app.approval.request_id !== undefined
columns: 4; Layout.fillWidth: true; columnSpacing: 10; rowSpacing: 8
Repeater {
model: [
{ l: "Closure", v: String(app.approval.closure_size !== undefined ? app.approval.closure_size : "—") },
{ l: "OSV", v: app.approval.osv_status || "—" },
{ l: "Vulnerabilities", v: String(app.approval.osv_vuln_count !== undefined ? app.approval.osv_vuln_count : "—") },
{ l: "Verified artifacts", v: String((app.approval.policy || {}).verified_artifacts !== undefined ? (app.approval.policy || {}).verified_artifacts : "—") },
{ l: "Decision", v: (app.approval.policy || {}).decision || "—" },
{ l: "Exit code", v: String((app.approval.policy || {}).exit_code !== undefined ? (app.approval.policy || {}).exit_code : "—") },
{ l: "Executed", v: (app.approval.policy || {}).executed ? "yes" : "no" },
{ l: "Request", v: String(app.approval.request_id || "—").substring(0, 12) }
]
Rectangle {
required property var modelData
Layout.fillWidth: true; Layout.preferredHeight: 58; color: Theme.raised; radius: Theme.radiusSm
Column {
anchors.centerIn: parent
Text { anchors.horizontalCenter: parent.horizontalCenter; text: modelData.l.toUpperCase(); color: Theme.faint; font.pixelSize: 8 }
Text { anchors.horizontalCenter: parent.horizontalCenter; text: modelData.v; color: Theme.text; font.pixelSize: 11; font.family: Theme.mono }
}
}
}
}
Text {
visible: text.length > 0
Layout.fillWidth: true
text: app.approval.error || ((app.approval.policy || {}).reason || "")
color: app.approval.error ? Theme.bad : Theme.dim
font.pixelSize: 11; font.family: Theme.mono; wrapMode: Text.Wrap
}
Rectangle {
visible: app.approvalBlockReason(app.selectedUpdate).length > 0
Layout.fillWidth: true
Layout.preferredHeight: blockText.implicitHeight + 22
color: Theme.raised; radius: Theme.radiusSm
Text {
id: blockText
anchors.fill: parent; anchors.margins: 11
text: app.approvalBlockReason(app.selectedUpdate)
color: Theme.dim; font.pixelSize: 11; wrapMode: Text.Wrap
}
}
CheckBox {
id: overrideToggle
visible: app.approvalBlockReason(app.selectedUpdate).length === 0 && !(app.approval.policy || {}).executed
text: "Override policy refusal with recorded operator intent"
checked: String(app.approval.error || "").indexOf("osv_status") >= 0
onToggled: if (!checked) overrideField.text = ""
contentItem: Text { text: parent.text; color: parent.checked ? Theme.warn : Theme.dim; font.pixelSize: 11; leftPadding: parent.indicator.width + parent.spacing }
}
TextArea {
id: overrideField
visible: overrideToggle.checked
Layout.fillWidth: true
Layout.preferredHeight: 76
placeholderText: "Why the operator is breaking glass. This reason is bound into the same authorization and durable history."
wrapMode: TextEdit.Wrap
color: Theme.text; placeholderTextColor: Theme.faint
background: Rectangle { color: Theme.surface; border.width: 1; border.color: overrideField.activeFocus ? Theme.red : Theme.divider; radius: Theme.radiusSm }
}
Text {
Layout.fillWidth: true; Layout.topMargin: 4
text: "Approval resolves the dependency closure, pins every artifact hash it can, checks the set against OSV, mints an Ed25519 capability bound to this host, and hands it to the privileged helper. The helper verifies independently. This window never touches a package manager."
color: Theme.faint; font.pixelSize: 10; wrapMode: Text.Wrap
}
}
}
footer: DialogButtonBox {
Button {
text: machine.approvalRunning ? "Authorizing…" : ((app.approval.policy || {}).executed ? "Installed" : overrideToggle.checked ? "Override and install" : "Approve and install")
enabled: !machine.approvalRunning && app.approvalBlockReason(app.selectedUpdate).length === 0 && !(app.approval.policy || {}).executed && (!overrideToggle.checked || overrideField.text.trim().length > 0)
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
onClicked: machine.approveUpdate(
app.selectedUpdate.package_type || "",
app.selectedUpdate.package_name || "",
app.selectedUpdate.available_version || "",
overrideToggle.checked ? overrideField.text.trim() : "")
background: Rectangle { color: !parent.enabled ? Theme.raised : parent.pressed ? Qt.darker(Theme.red, 1.15) : Theme.red; radius: Theme.radiusSm }
contentItem: Text { text: parent.text; color: parent.enabled ? "white" : Theme.faint; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; font.pixelSize: 11; font.weight: 650 }
}
Button {
text: "Close"
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole
onClicked: updateDialog.close()
background: Rectangle { color: parent.pressed ? Theme.hover : "transparent"; border.width: 1; border.color: Theme.divider; radius: Theme.radiusSm }
contentItem: Text { text: parent.text; color: Theme.dim; horizontalAlignment: Text.AlignHCenter; verticalAlignment: Text.AlignVCenter; font.pixelSize: 11 }
}
}
onOpened: {
overrideField.text = ""
overrideToggle.checked = false
}
onClosed: machine.refreshOverview()
}
}