Compare commits

...
2 Commits
Author SHA1 Message Date
Hannes 0a0d8130d0 update to network stat graphs 2026-08-22 00:24:17 +02:00
Hannes e5e1d34e5c update to network updates 2026-08-22 00:12:37 +02:00
2 changed files with 180 additions and 129 deletions
+156 -113
View File
@@ -15,9 +15,9 @@ QtObject {
property string uploadSpeed: "" property string uploadSpeed: ""
property string ping: "" property string ping: ""
property var downloadHistory: [] property var downloadHistory: [{time: Date.now() - 60000, value: 0}, {time: Date.now(), value: 0}]
property var uploadHistory: [] property var uploadHistory: [{time: Date.now() - 60000, value: 0}, {time: Date.now(), value: 0}]
property var pingHistory: [] property var pingHistory: [{time: Date.now() - 60000, value: 0}, {time: Date.now(), value: 0}]
property int _maxHistory: 60 property int _maxHistory: 60
property bool internetAvailable: Networking.connectivity === NetworkConnectivity.Full property bool internetAvailable: Networking.connectivity === NetworkConnectivity.Full
property bool vpnConnected: false property bool vpnConnected: false
@@ -34,6 +34,7 @@ QtObject {
property real _prevTxBytes: 0 property real _prevTxBytes: 0
property var _prevUpdateTime: 0 property var _prevUpdateTime: 0
property string _trafficActiveDevice: "" property string _trafficActiveDevice: ""
property string _lastNetworkSignature: ""
readonly property var networking: Networking readonly property var networking: Networking
@@ -53,11 +54,32 @@ QtObject {
return "" return ""
} }
property var _pendingDevices: []
function updateAll() { function updateAll() {
updateNetworks(true)
updateStats()
}
function updateNetworks(force) {
var connected = findAllConnectedDevices() var connected = findAllConnectedDevices()
_pendingDevices = connected
var sig = ""
for (var i = 0; i < connected.length; i++) {
var dev = connected[i]
sig += dev.name + ":" + dev.type + ":" + dev.connected
if (dev.type === DeviceType.Wifi) {
var nets = dev.networks.values
for (var j = 0; j < nets.length; j++) {
if (nets[j].connected) {
sig += ":" + nets[j].name
break
}
}
}
sig += ";"
}
if (!force && sig === _lastNetworkSignature) return
_lastNetworkSignature = sig
activeConnections.clear() activeConnections.clear()
var hasWifi = false var hasWifi = false
@@ -109,7 +131,6 @@ QtObject {
if (trafficDev) { if (trafficDev) {
_trafficActiveDevice = trafficDev.name _trafficActiveDevice = trafficDev.name
trafficScript.running = true
} }
root.computeStatus(hasWired, hasWifi, bestWifiSignal) root.computeStatus(hasWired, hasWifi, bestWifiSignal)
@@ -117,13 +138,27 @@ QtObject {
if (connected.length > 0) { if (connected.length > 0) {
ipv4Script.running = true ipv4Script.running = true
} }
networkDetailScript.running = true
}
function updateStats() {
if (!_trafficActiveDevice) return
statsScript.running = true
} }
property Timer networkTimer: Timer { property Timer networkTimer: Timer {
interval: 10000 interval: 5000
repeat: true repeat: true
running: true running: true
onTriggered: root.updateAll() onTriggered: root.updateNetworks()
}
property Timer statsTimer: Timer {
interval: 1000
repeat: true
running: true
onTriggered: root.updateStats()
} }
property Process ipv4Script: Process { property Process ipv4Script: Process {
@@ -161,16 +196,8 @@ QtObject {
} }
} }
property Process trafficScript: Process { property Process networkDetailScript: Process {
command: ["sh", "-c", command: ["sh", "-c",
"echo '===TRAFFIC==='; " +
"DEV=\"" + root._trafficActiveDevice + "\"; " +
"if [ -n \"$DEV\" ]; then " +
" echo \"RX_BYTES=$(cat /sys/class/net/$DEV/statistics/rx_bytes 2>/dev/null || echo 0)\"; " +
" echo \"TX_BYTES=$(cat /sys/class/net/$DEV/statistics/tx_bytes 2>/dev/null || echo 0)\"; " +
"fi; " +
"echo '===PING==='; " +
"ping -c 1 -W 2 1.1.1.1 2>/dev/null | awk -F/ '/rtt/{print $5}' || echo ''; " +
"echo '===VPN_ALL==='; " + "echo '===VPN_ALL==='; " +
"nmcli -t -f NAME,TYPE connection show 2>/dev/null | grep -i vpn || true; " + "nmcli -t -f NAME,TYPE connection show 2>/dev/null | grep -i vpn || true; " +
"echo '===VPN_ACTIVE==='; " + "echo '===VPN_ACTIVE==='; " +
@@ -182,7 +209,117 @@ QtObject {
] ]
stdout: StdioCollector { stdout: StdioCollector {
onStreamFinished: root.parseOutput(text.trim()) onStreamFinished: root.parseNetworkDetails(text.trim())
}
}
function parseNetworkDetails(output) {
var sections = output.split("===TAILSCALE_IP===")
var tailscaleIpSection = sections.length > 1 ? sections[1].trim() : ""
var beforeTsIp = sections[0]
sections = beforeTsIp.split("===TAILSCALE_EXISTS===")
var tailscaleExistsSection = sections.length > 1 ? sections[1].trim() : ""
var beforeTsExist = sections[0]
sections = beforeTsExist.split("===VPN_ACTIVE===")
var vpnActiveSection = sections.length > 1 ? sections[1].trim() : ""
var beforeVpnActive = sections[0]
sections = beforeVpnActive.split("===VPN_ALL===")
var vpnAllSection = sections.length > 1 ? sections[1].trim() : ""
root.vpnExists = vpnAllSection.length > 0
if (vpnActiveSection) {
root.vpnConnected = true
var vpnLines = vpnActiveSection.split("\n")
if (vpnLines.length > 0) {
root.vpnName = vpnLines[0].split(":")[0] || ""
}
} else {
root.vpnConnected = false
root.vpnName = ""
root.vpnIp = ""
}
root.tailscaleExists = tailscaleExistsSection.indexOf("EXISTS") >= 0
if (tailscaleIpSection) {
root.tailscaleConnected = true
root.tailscaleIp = tailscaleIpSection
} else {
root.tailscaleConnected = false
root.tailscaleIp = ""
}
}
property Process statsScript: Process {
command: ["sh", "-c",
"echo '===TRAFFIC==='; " +
"DEV=\"" + root._trafficActiveDevice + "\"; " +
"if [ -n \"$DEV\" ]; then " +
" echo \"RX_BYTES=$(cat /sys/class/net/$DEV/statistics/rx_bytes 2>/dev/null || echo 0)\"; " +
" echo \"TX_BYTES=$(cat /sys/class/net/$DEV/statistics/tx_bytes 2>/dev/null || echo 0)\"; " +
"fi; " +
"echo '===PING==='; " +
"ping -c 1 -W 2 1.1.1.1 2>/dev/null | awk -F/ '/rtt/{print $5}' || echo ''"
]
stdout: StdioCollector {
onStreamFinished: root.parseStats(text.trim())
}
}
function parseStats(output) {
var sections = output.split("===PING===")
root.ping = sections.length > 1 ? sections[1].trim() : ""
var pingVal = parseFloat(root.ping)
if (!isNaN(pingVal) && pingVal > 0) {
var pingHist = root.pingHistory.slice()
pingHist.push({time: Date.now(), value: pingVal})
if (pingHist.length > root._maxHistory) pingHist.shift()
root.pingHistory = pingHist
}
var trafficSection = sections[0].replace("===TRAFFIC===", "").trim()
if (trafficSection) {
var trafficLines = trafficSection.split("\n")
var rxBytes = 0
var txBytes = 0
for (var t = 0; t < trafficLines.length; t++) {
var tline = trafficLines[t].trim()
if (tline.indexOf("RX_BYTES=") === 0) rxBytes = parseFloat(tline.substring("RX_BYTES=".length))
else if (tline.indexOf("TX_BYTES=") === 0) txBytes = parseFloat(tline.substring("TX_BYTES=".length))
}
if (root._prevRxBytes > 0 && root._prevUpdateTime > 0) {
var now = Date.now()
var interval = (now - root._prevUpdateTime) / 1000
if (interval > 0) {
var dlBytes = (rxBytes - root._prevRxBytes) / interval
var ulBytes = (txBytes - root._prevTxBytes) / interval
root.downloadSpeed = root.formatSpeed(dlBytes)
root.uploadSpeed = root.formatSpeed(ulBytes)
var dlHist = root.downloadHistory.slice()
dlHist.push({time: now, value: dlBytes})
if (dlHist.length > root._maxHistory) dlHist.shift()
root.downloadHistory = dlHist
var ulHist = root.uploadHistory.slice()
ulHist.push({time: now, value: ulBytes})
if (ulHist.length > root._maxHistory) ulHist.shift()
root.uploadHistory = ulHist
}
} else {
root.downloadSpeed = ""
root.uploadSpeed = ""
}
root._prevRxBytes = rxBytes
root._prevTxBytes = txBytes
root._prevUpdateTime = Date.now()
} }
} }
@@ -214,100 +351,6 @@ QtObject {
return bytesPerSec.toFixed(0) + " B/s" return bytesPerSec.toFixed(0) + " B/s"
} }
function parseOutput(output) {
var sections = output.split("===TAILSCALE_IP===")
var tailscaleIpSection = sections.length > 1 ? sections[1].trim() : ""
var beforeTsIp = sections[0]
sections = beforeTsIp.split("===TAILSCALE_EXISTS===")
var tailscaleExistsSection = sections.length > 1 ? sections[1].trim() : ""
var beforeTsExist = sections[0]
sections = beforeTsExist.split("===VPN_ACTIVE===")
var vpnActiveSection = sections.length > 1 ? sections[1].trim() : ""
var beforeVpnActive = sections[0]
sections = beforeVpnActive.split("===VPN_ALL===")
var vpnAllSection = sections.length > 1 ? sections[1].trim() : ""
var beforeVpnAll = sections[0]
sections = beforeVpnAll.split("===PING===")
root.ping = sections.length > 1 ? sections[1].trim() : ""
var pingVal = parseFloat(root.ping)
if (!isNaN(pingVal) && pingVal > 0) {
var pingHist = root.pingHistory.slice()
pingHist.push(pingVal)
if (pingHist.length > root._maxHistory) pingHist.shift()
root.pingHistory = pingHist
}
var beforePing = sections[0]
sections = beforePing.split("===TRAFFIC===")
var trafficSection = sections.length > 1 ? sections[1].trim() : ""
if (trafficSection) {
var trafficLines = trafficSection.split("\n")
var rxBytes = 0
var txBytes = 0
for (var t = 0; t < trafficLines.length; t++) {
var tline = trafficLines[t].trim()
if (tline.indexOf("RX_BYTES=") === 0) rxBytes = parseFloat(tline.substring("RX_BYTES=".length))
else if (tline.indexOf("TX_BYTES=") === 0) txBytes = parseFloat(tline.substring("TX_BYTES=".length))
}
if (root._prevRxBytes > 0 && root._prevUpdateTime > 0) {
var now = Date.now()
var interval = (now - root._prevUpdateTime) / 1000
if (interval > 0) {
var dlBytes = (rxBytes - root._prevRxBytes) / interval
var ulBytes = (txBytes - root._prevTxBytes) / interval
root.downloadSpeed = root.formatSpeed(dlBytes)
root.uploadSpeed = root.formatSpeed(ulBytes)
var dlHist = root.downloadHistory.slice()
dlHist.push(dlBytes)
if (dlHist.length > root._maxHistory) dlHist.shift()
root.downloadHistory = dlHist
var ulHist = root.uploadHistory.slice()
ulHist.push(ulBytes)
if (ulHist.length > root._maxHistory) ulHist.shift()
root.uploadHistory = ulHist
}
} else {
root.downloadSpeed = ""
root.uploadSpeed = ""
}
root._prevRxBytes = rxBytes
root._prevTxBytes = txBytes
root._prevUpdateTime = Date.now()
}
root.vpnExists = vpnAllSection.length > 0
if (vpnActiveSection) {
root.vpnConnected = true
var vpnLines = vpnActiveSection.split("\n")
if (vpnLines.length > 0) {
root.vpnName = vpnLines[0].split(":")[0] || ""
}
} else {
root.vpnConnected = false
root.vpnName = ""
root.vpnIp = ""
}
root.tailscaleExists = tailscaleExistsSection.indexOf("EXISTS") >= 0
if (tailscaleIpSection) {
root.tailscaleConnected = true
root.tailscaleIp = tailscaleIpSection
} else {
root.tailscaleConnected = false
root.tailscaleIp = ""
}
}
Component.onCompleted: { Component.onCompleted: {
root.updateAll() root.updateAll()
} }
+24 -16
View File
@@ -7,7 +7,7 @@ Item {
property var values: [] property var values: []
property color lineColor: Colors.primary property color lineColor: Colors.primary
property color fillColor: Qt.alpha(Colors.primary, 0.15) property color fillColor: Qt.alpha(Colors.primary, 0.15)
property int pointCount: 60 property real windowMs: 60000
property real minWidth: 120 property real minWidth: 120
property real graphHeight: 30 property real graphHeight: 30
@@ -25,26 +25,34 @@ Item {
var data = root.values var data = root.values
if (!data || data.length < 2) return if (!data || data.length < 2) return
var displayCount = Math.min(data.length, root.pointCount) var now = Date.now()
var startIdx = data.length - displayCount var windowStart = now - root.windowMs
var slice = data.slice(startIdx)
var maxVal = 0
for (var i = 0; i < slice.length; i++) {
if (slice[i] > maxVal) maxVal = slice[i]
}
if (maxVal === 0) maxVal = 1
var stepX = width / (slice.length - 1)
var padY = 2 var padY = 2
var drawH = height - padY * 2 var drawH = height - padY * 2
var maxVal = 0
for (var i = 0; i < data.length; i++) {
if (data[i].time >= windowStart && data[i].value > maxVal)
maxVal = data[i].value
}
if (maxVal === 0) maxVal = 1
ctx.beginPath() ctx.beginPath()
ctx.moveTo(0, height - padY - (slice[0] / maxVal) * drawH) var started = false
for (var j = 1; j < slice.length; j++) { for (var j = 0; j < data.length; j++) {
var x = j * stepX var pt = data[j]
var y = height - padY - (slice[j] / maxVal) * drawH if (pt.time < windowStart) continue
ctx.lineTo(x, y)
var x = ((pt.time - windowStart) / root.windowMs) * width
var y = height - padY - (pt.value / maxVal) * drawH
if (!started) {
ctx.moveTo(x, y)
started = true
} else {
ctx.lineTo(x, y)
}
} }
ctx.strokeStyle = root.lineColor ctx.strokeStyle = root.lineColor
ctx.lineWidth = 1.5 ctx.lineWidth = 1.5