The public tree and its history contain only the listed paths. Earlier projection history remains preserved internally. Source-Sha: 913fde029b935671833254797f0f20f1eb9fabba Policy-Sha: 913fde029b935671833254797f0f20f1eb9fabba Tree-Digest: 180ae530c1058a2a5c89837bdce2d323ae83e669e38590ca72e75b8d92b7262f
173 lines
8 KiB
QML
173 lines
8 KiB
QML
import QtQuick
|
||
import QtQuick.Controls.Basic
|
||
import QtQuick.Layouts
|
||
|
||
Item {
|
||
id: root
|
||
property var events: []
|
||
property bool loading: false
|
||
property string query: ""
|
||
property var selected: ({})
|
||
signal refreshRequested()
|
||
readonly property var filtered: events.filter(e => {
|
||
if (!e) return false;
|
||
if (severity.currentIndex === 1 && e.severity !== "error" && e.severity !== "critical") return false;
|
||
if (severity.currentIndex === 2 && e.severity !== "warning") return false;
|
||
if (kind.currentIndex === 1 && e.event_type !== "agent_scan") return false;
|
||
if (kind.currentIndex === 2 && e.event_type !== "agent_install" && e.event_type !== "agent_update") return false;
|
||
return !query.length || JSON.stringify(e).toLowerCase().indexOf(query.toLowerCase()) >= 0;
|
||
})
|
||
|
||
function eventColor(e) {
|
||
return e.severity === "error" || e.severity === "critical" ? Theme.bad : e.severity === "warning" ? Theme.warn : e.event_subtype === "success" ? Theme.good : Theme.blue;
|
||
}
|
||
function stamp(e) { return e.created_at ? new Date(e.created_at).toLocaleString() : "Time not reported"; }
|
||
|
||
ColumnLayout {
|
||
anchors.fill: parent
|
||
anchors.margins: width < 760 ? 12 : 24
|
||
spacing: 12
|
||
PageHeader {
|
||
Layout.fillWidth: true
|
||
title: "History"
|
||
subtitle: "Scans, update outcomes, and machine events retained here after fleet synchronization. Latest 5,000 events."
|
||
AppButton { text: root.loading ? "Reading…" : "Refresh"; enabled: !root.loading; onClicked: root.refreshRequested() }
|
||
}
|
||
GridLayout {
|
||
Layout.fillWidth: true
|
||
columns: width < 720 ? 1 : 3
|
||
TextField {
|
||
id: search
|
||
Layout.fillWidth: true
|
||
placeholderText: "Find a package, operation, message, or reason"
|
||
color: Theme.text
|
||
placeholderTextColor: Theme.faint
|
||
selectByMouse: true
|
||
onTextChanged: debounce.restart()
|
||
background: Rectangle { color: Theme.surface; border.color: search.activeFocus ? Theme.blue : Theme.divider; radius: Theme.radiusSm }
|
||
Timer { id: debounce; interval: 180; onTriggered: root.query = search.text.trim() }
|
||
}
|
||
ComboBox { id: kind; model: ["All activity", "Scans", "Updates & installs"]; Layout.fillWidth: parent.columns === 1 }
|
||
ComboBox { id: severity; model: ["All outcomes", "Errors & critical", "Warnings"]; Layout.fillWidth: parent.columns === 1 }
|
||
}
|
||
Rectangle {
|
||
Layout.fillWidth: true
|
||
implicitHeight: 28
|
||
color: Theme.titlebar
|
||
Text {
|
||
anchors { left: parent.left; right: parent.right; verticalCenter: parent.verticalCenter; margins: 10 }
|
||
text: "ACTIVITY REGISTER " + root.filtered.length + " / " + root.events.length + " events"
|
||
color: "white"
|
||
font { family: Theme.mono; pixelSize: Theme.fontMeta; bold: true }
|
||
}
|
||
}
|
||
ListView {
|
||
id: list
|
||
Layout.fillWidth: true
|
||
Layout.fillHeight: true
|
||
clip: true
|
||
model: root.filtered
|
||
spacing: 1
|
||
ScrollBar.vertical: ScrollBar {}
|
||
Text {
|
||
anchors.centerIn: parent
|
||
width: Math.min(parent.width - 32, 440)
|
||
horizontalAlignment: Text.AlignHCenter
|
||
wrapMode: Text.Wrap
|
||
text: root.loading ? "Reading local history…" : root.events.length ? "No events match these filters." : "No retained events yet. New scans and update outcomes will appear here."
|
||
visible: !list.count
|
||
color: Theme.dim
|
||
font.pixelSize: Theme.fontBody
|
||
}
|
||
delegate: ItemDelegate {
|
||
id: row
|
||
required property var modelData
|
||
width: list.width
|
||
implicitHeight: 78
|
||
onClicked: { root.selected = modelData; detail.open(); }
|
||
background: Rectangle { color: row.hovered || row.activeFocus ? Theme.selection : Theme.surface; border.color: row.activeFocus ? Theme.blue : "transparent" }
|
||
contentItem: RowLayout {
|
||
spacing: 12
|
||
Rectangle { width: 4; Layout.fillHeight: true; color: root.eventColor(row.modelData) }
|
||
ColumnLayout {
|
||
Layout.fillWidth: true
|
||
spacing: 4
|
||
Text {
|
||
Layout.fillWidth: true
|
||
text: row.modelData.message || row.modelData.event_type
|
||
textFormat: Text.PlainText
|
||
elide: Text.ElideRight
|
||
color: Theme.text
|
||
font.pixelSize: Theme.fontBody
|
||
}
|
||
Text {
|
||
Layout.fillWidth: true
|
||
text: root.stamp(row.modelData) + " · " + (row.modelData.component || "agent") + " · " + String(row.modelData.event_subtype || "event").replace(/_/g, " ")
|
||
elide: Text.ElideRight
|
||
color: Theme.dim
|
||
font.pixelSize: Theme.fontMeta
|
||
}
|
||
}
|
||
Text { text: "›"; color: Theme.blue; font.pixelSize: 22 }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Dialog {
|
||
id: detail
|
||
parent: Overlay.overlay
|
||
anchors.centerIn: parent
|
||
width: Math.min(parent.width - 32, 820)
|
||
height: Math.min(parent.height - 48, 640)
|
||
modal: true
|
||
title: "Event detail"
|
||
standardButtons: Dialog.Close
|
||
background: Rectangle { color: Theme.window; border.color: Theme.blue; border.width: 1; radius: Theme.radius }
|
||
contentItem: ScrollView {
|
||
clip: true
|
||
contentWidth: availableWidth
|
||
ColumnLayout {
|
||
width: detail.availableWidth
|
||
spacing: 12
|
||
Text {
|
||
Layout.fillWidth: true
|
||
text: root.stamp(root.selected) + " · " + String(root.selected.severity || "info").toUpperCase()
|
||
color: root.eventColor(root.selected)
|
||
wrapMode: Text.Wrap
|
||
font { pixelSize: Theme.fontMeta; bold: true }
|
||
}
|
||
TextArea {
|
||
Layout.fillWidth: true
|
||
text: root.selected.message || ""
|
||
textFormat: TextEdit.PlainText
|
||
readOnly: true
|
||
selectByMouse: true
|
||
wrapMode: TextEdit.Wrap
|
||
color: Theme.text
|
||
font.pixelSize: Theme.fontBody
|
||
background: Rectangle { color: Theme.surface; border.color: Theme.divider }
|
||
}
|
||
Text {
|
||
Layout.fillWidth: true
|
||
text: (root.selected.component || "agent") + " / " + (root.selected.event_type || "event") + " / " + (root.selected.event_subtype || "")
|
||
color: Theme.dim
|
||
wrapMode: Text.Wrap
|
||
font.pixelSize: Theme.fontMeta
|
||
}
|
||
Text { text: "RECORDED CONTEXT"; color: Theme.blue; font { pixelSize: Theme.fontMeta; bold: true; letterSpacing: 1 } }
|
||
TextArea {
|
||
Layout.fillWidth: true
|
||
text: JSON.stringify(root.selected.metadata || {}, null, 2)
|
||
textFormat: TextEdit.PlainText
|
||
readOnly: true
|
||
selectByMouse: true
|
||
wrapMode: TextEdit.Wrap
|
||
color: Theme.dim
|
||
font { family: Theme.mono; pixelSize: Theme.fontMeta }
|
||
background: Rectangle { color: Theme.surface; border.color: Theme.divider }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|