RedFlag/desktop/qml/HistoryPanel.qml
Fimeg 67e26be2d9 projection: begin the exact-path public epoch
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
2026-09-08 21:59:33 -04:00

173 lines
8 KiB
QML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 }
}
}
}
}
}