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
36
desktop/qml/AppButton.qml
Normal file
36
desktop/qml/AppButton.qml
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls.Basic
|
||||
|
||||
Button {
|
||||
id: control
|
||||
|
||||
implicitHeight: Math.max(36, contentItem.implicitHeight + 16)
|
||||
implicitWidth: contentItem.implicitWidth + 28
|
||||
leftPadding: 14
|
||||
rightPadding: 14
|
||||
topPadding: 8
|
||||
bottomPadding: 8
|
||||
font.pixelSize: Theme.fontBody
|
||||
|
||||
background: Rectangle {
|
||||
radius: Theme.radiusSm
|
||||
color: control.down ? Theme.hover : control.hovered ? Theme.hover : Theme.raised
|
||||
border.width: 1
|
||||
border.color: control.activeFocus ? Theme.blue : Theme.bevelDark
|
||||
opacity: control.enabled ? 1 : 0.5
|
||||
Rectangle {
|
||||
anchors { left: parent.left; right: parent.right; top: parent.top; margins: 1 }
|
||||
height: 1
|
||||
color: control.down ? Theme.bevelDark : Theme.bevelLight
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font: control.font
|
||||
color: control.enabled ? Theme.text : Theme.faint
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
|
||||
}
|
||||
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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
45
desktop/qml/LineGraph.qml
Normal file
45
desktop/qml/LineGraph.qml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import QtQuick
|
||||
|
||||
Canvas {
|
||||
id: root
|
||||
property var values: []
|
||||
property real ceiling: 100
|
||||
property color lineColor: Theme.red
|
||||
property color fillColor: Theme.tint(lineColor, 0.12)
|
||||
property bool grid: true
|
||||
|
||||
onValuesChanged: requestPaint()
|
||||
onWidthChanged: requestPaint()
|
||||
onHeightChanged: requestPaint()
|
||||
onCeilingChanged: requestPaint()
|
||||
|
||||
onPaint: {
|
||||
const ctx = getContext("2d")
|
||||
ctx.reset()
|
||||
if (grid) {
|
||||
ctx.strokeStyle = Theme.divider
|
||||
ctx.lineWidth = 1
|
||||
for (let y = 0; y <= 4; ++y) {
|
||||
ctx.beginPath(); ctx.moveTo(0, y * height / 4); ctx.lineTo(width, y * height / 4); ctx.stroke()
|
||||
}
|
||||
}
|
||||
if (!values || values.length < 2) return
|
||||
const top = Math.max(1, ceiling)
|
||||
const step = width / Math.max(1, values.length - 1)
|
||||
ctx.beginPath()
|
||||
for (let i = 0; i < values.length; ++i) {
|
||||
const x = i * step
|
||||
const y = height - Math.min(top, Math.max(0, Number(values[i]))) / top * height
|
||||
if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y)
|
||||
}
|
||||
ctx.lineTo(width, height); ctx.lineTo(0, height); ctx.closePath()
|
||||
ctx.fillStyle = fillColor; ctx.fill()
|
||||
ctx.beginPath()
|
||||
for (let j = 0; j < values.length; ++j) {
|
||||
const x2 = j * step
|
||||
const y2 = height - Math.min(top, Math.max(0, Number(values[j]))) / top * height
|
||||
if (j === 0) ctx.moveTo(x2, y2); else ctx.lineTo(x2, y2)
|
||||
}
|
||||
ctx.strokeStyle = lineColor; ctx.lineWidth = 2; ctx.stroke()
|
||||
}
|
||||
}
|
||||
4131
desktop/qml/Main.qml
Normal file
4131
desktop/qml/Main.qml
Normal file
File diff suppressed because it is too large
Load diff
62
desktop/qml/MetricCard.qml
Normal file
62
desktop/qml/MetricCard.qml
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
required property string label
|
||||
required property string value
|
||||
property string detail: ""
|
||||
property color accent: Theme.text
|
||||
|
||||
implicitWidth: 180
|
||||
implicitHeight: 108
|
||||
radius: Theme.radius
|
||||
color: Theme.surface
|
||||
border.width: 1
|
||||
border.color: Theme.divider
|
||||
|
||||
Rectangle {
|
||||
width: 3
|
||||
height: 28
|
||||
radius: 2
|
||||
color: parent.accent
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 13
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 16
|
||||
}
|
||||
|
||||
Text {
|
||||
text: parent.label.toUpperCase()
|
||||
color: Theme.faint
|
||||
font.pixelSize: Theme.fontMeta
|
||||
font.letterSpacing: 1.1
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 26
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 14
|
||||
}
|
||||
|
||||
Text {
|
||||
text: parent.value
|
||||
color: parent.accent
|
||||
font.pixelSize: 22
|
||||
font.weight: 650
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 26
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 36
|
||||
}
|
||||
|
||||
Text {
|
||||
text: parent.detail
|
||||
color: Theme.dim
|
||||
font.pixelSize: Theme.fontMeta
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 26
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 11
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
}
|
||||
55
desktop/qml/NavItem.qml
Normal file
55
desktop/qml/NavItem.qml
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
import QtQuick
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
required property string label
|
||||
property string badge: ""
|
||||
property bool selected: false
|
||||
signal clicked()
|
||||
|
||||
// A ColumnLayout allocates rows from implicitHeight. A plain Rectangle has
|
||||
// none, so an explicit height laid out as a zero-high row and every entry
|
||||
// drew on top of its neighbour.
|
||||
implicitWidth: 180
|
||||
implicitHeight: 38
|
||||
radius: Theme.radiusSm
|
||||
color: selected ? Theme.selection : mouse.containsMouse ? Theme.hover : "transparent"
|
||||
|
||||
Rectangle {
|
||||
width: 3
|
||||
height: 18
|
||||
radius: 2
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 2
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: root.selected ? Theme.blue : "transparent"
|
||||
}
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 16
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.label
|
||||
color: root.selected ? Theme.text : Theme.dim
|
||||
font.pixelSize: Theme.fontBody
|
||||
font.weight: root.selected ? 600 : 450
|
||||
}
|
||||
Rectangle {
|
||||
visible: root.badge.length > 0
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: Math.max(20, badgeText.implicitWidth + 10)
|
||||
height: 20
|
||||
radius: 10
|
||||
color: Theme.tint(root.selected ? Theme.red : Theme.faint, 0.22)
|
||||
Text {
|
||||
id: badgeText
|
||||
anchors.centerIn: parent
|
||||
text: root.badge
|
||||
color: root.selected ? Theme.red : Theme.dim
|
||||
font.pixelSize: 10
|
||||
font.weight: 700
|
||||
}
|
||||
}
|
||||
MouseArea { id: mouse; anchors.fill: parent; hoverEnabled: true; onClicked: root.clicked() }
|
||||
}
|
||||
47
desktop/qml/PageHeader.qml
Normal file
47
desktop/qml/PageHeader.qml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
GridLayout {
|
||||
id: root
|
||||
|
||||
property string title
|
||||
property string subtitle
|
||||
default property alias actions: actionsRow.data
|
||||
|
||||
columns: width < 850 ? 1 : 2
|
||||
columnSpacing: 20
|
||||
rowSpacing: 12
|
||||
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.minimumWidth: 0
|
||||
spacing: 6
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: root.title
|
||||
color: Theme.text
|
||||
font.pixelSize: Theme.fontTitle
|
||||
font.weight: Font.Bold
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: root.subtitle
|
||||
color: Theme.dim
|
||||
font.pixelSize: Theme.fontBody
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
id: actionsRow
|
||||
|
||||
Layout.fillWidth: root.columns === 1
|
||||
Layout.maximumWidth: root.width
|
||||
spacing: 8
|
||||
}
|
||||
|
||||
}
|
||||
92
desktop/qml/ScanPanel.qml
Normal file
92
desktop/qml/ScanPanel.qml
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import QtQuick
|
||||
import QtQuick.Controls.Basic
|
||||
import QtQuick.Layouts
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
property var snapshot: ({})
|
||||
property bool running: false
|
||||
readonly property var scanners: Object.keys(snapshot.scanners || {}).sort().map(key => snapshot.scanners[key])
|
||||
implicitHeight: body.implicitHeight + 24
|
||||
color: Theme.surface
|
||||
border.color: Theme.divider
|
||||
radius: Theme.radius
|
||||
|
||||
function stamp(value) {
|
||||
return !value || String(value).indexOf("0001-") === 0 ? "Not scanned yet" : new Date(value).toLocaleString();
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: body
|
||||
anchors { left: parent.left; right: parent.right; top: parent.top; margins: 12 }
|
||||
spacing: 8
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Rectangle { width: 7; height: 7; color: root.running ? Theme.warn : Theme.blue }
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: root.running ? "SCANNING THIS MACHINE" : "SCAN STATUS"
|
||||
color: Theme.blue
|
||||
font { pixelSize: Theme.fontMeta; bold: true; letterSpacing: 1 }
|
||||
}
|
||||
Text {
|
||||
text: root.stamp(root.snapshot.last_scan_time)
|
||||
color: Theme.dim
|
||||
font.pixelSize: Theme.fontMeta
|
||||
}
|
||||
}
|
||||
Text {
|
||||
visible: !root.scanners.length
|
||||
Layout.fillWidth: true
|
||||
text: root.running ? "The Agent is checking available package sources." : "No scanner results yet. Run a scan to establish update coverage."
|
||||
color: Theme.dim
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: Theme.fontBody
|
||||
}
|
||||
Repeater {
|
||||
model: root.scanners
|
||||
ColumnLayout {
|
||||
required property var modelData
|
||||
Layout.fillWidth: true
|
||||
spacing: 3
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Text {
|
||||
text: String(modelData.name).toUpperCase()
|
||||
Layout.preferredWidth: 90
|
||||
color: Theme.text
|
||||
font { family: Theme.mono; pixelSize: Theme.fontMeta; bold: true }
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.status === "success" ? modelData.update_count + " available" : String(modelData.status || "unknown")
|
||||
color: modelData.status === "success" ? Theme.good : Theme.warn
|
||||
font.pixelSize: Theme.fontMeta
|
||||
}
|
||||
Text {
|
||||
text: (Number(modelData.last_duration_ms || 0) / 1000).toFixed(1) + "s · " + root.stamp(modelData.last_scan_time)
|
||||
color: Theme.faint
|
||||
font.pixelSize: Theme.fontMeta
|
||||
}
|
||||
}
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
visible: Boolean(modelData.last_error)
|
||||
text: modelData.last_error || ""
|
||||
textFormat: Text.PlainText
|
||||
color: Theme.bad
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: Theme.fontMeta
|
||||
}
|
||||
}
|
||||
}
|
||||
Text {
|
||||
visible: root.scanners.some(s => s.status !== "success")
|
||||
Layout.fillWidth: true
|
||||
text: "A source did not complete. Its previous update observations may still be listed below."
|
||||
color: Theme.warn
|
||||
wrapMode: Text.Wrap
|
||||
font.pixelSize: Theme.fontMeta
|
||||
}
|
||||
}
|
||||
}
|
||||
49
desktop/qml/Theme.qml
Normal file
49
desktop/qml/Theme.qml
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
pragma Singleton
|
||||
import QtQuick
|
||||
|
||||
QtObject {
|
||||
property bool dark: true
|
||||
|
||||
readonly property color window: dark ? "#1b1d1e" : "#e5e5df"
|
||||
readonly property color rail: dark ? "#181a1b" : "#ffffff"
|
||||
readonly property color surface: dark ? "#181a1b" : "#ffffff"
|
||||
readonly property color raised: dark ? "#252a2e" : "#efefe9"
|
||||
readonly property color hover: dark ? "#2a2d2f" : "#eef2f6"
|
||||
readonly property color divider: dark ? "#393d40" : "#dce1e7"
|
||||
readonly property color text: dark ? "#e5e2dc" : "#171a1f"
|
||||
readonly property color dim: dark ? "#b2aca2" : "#5f6874"
|
||||
readonly property color faint: dark ? "#979084" : "#8a939e"
|
||||
readonly property color red: "#ef2525"
|
||||
readonly property color redDim: dark ? "#380b0b" : "#fee2e2"
|
||||
readonly property color good: "#32bd74"
|
||||
readonly property color warn: "#e5a936"
|
||||
readonly property color bad: "#ef5350"
|
||||
readonly property color blue: dark ? "#85add6" : "#336699"
|
||||
readonly property color selection: dark ? "#23394b" : "#d9e6f2"
|
||||
readonly property color titlebar: "#336699"
|
||||
readonly property color bevelLight: dark ? "#465057" : "#ffffff"
|
||||
readonly property color bevelDark: dark ? "#111517" : "#a4aaa9"
|
||||
readonly property color purple: "#ad7aff"
|
||||
readonly property color cyan: "#30b8c9"
|
||||
|
||||
readonly property int railWidth: 216
|
||||
readonly property int headerHeight: 72
|
||||
readonly property int radius: 3
|
||||
readonly property int radiusSm: 2
|
||||
readonly property int spacing: 14
|
||||
readonly property int spacingSm: 8
|
||||
readonly property int fontTitle: 23
|
||||
readonly property int fontHeading: 17
|
||||
readonly property int fontBody: 14
|
||||
readonly property int fontMeta: 12
|
||||
readonly property string mono: {
|
||||
const wanted = ["JetBrainsMono NF", "Cascadia Mono", "Consolas", "Adwaita Mono", "DejaVu Sans Mono"]
|
||||
const installed = Qt.fontFamilies()
|
||||
for (let i = 0; i < wanted.length; ++i)
|
||||
if (installed.indexOf(wanted[i]) >= 0) return wanted[i]
|
||||
return "monospace"
|
||||
}
|
||||
|
||||
function tint(color, alpha) { return Qt.rgba(color.r, color.g, color.b, alpha) }
|
||||
function pressure(percent) { return percent >= 90 ? bad : percent >= 75 ? warn : good }
|
||||
}
|
||||
Loading…
Reference in a new issue