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
This commit is contained in:
commit
67e26be2d9
838 changed files with 157302 additions and 0 deletions
173
desktop/qml/HistoryPanel.qml
Normal file
173
desktop/qml/HistoryPanel.qml
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue