updates to networking
This commit is contained in:
@@ -2,77 +2,211 @@ pragma Singleton
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import "../../../../config"
|
import Quickshell.Networking
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
property string status: "..."
|
property string status: "..."
|
||||||
|
|
||||||
property string interfaceName: ""
|
property ListModel activeConnections: ListModel {}
|
||||||
property string interfaceType: ""
|
|
||||||
property string connectionName: ""
|
|
||||||
property string ipv4: ""
|
|
||||||
property string wifiName: ""
|
|
||||||
property string signalStrength: ""
|
|
||||||
property string wifiChannel: ""
|
|
||||||
property string wifiFrequency: ""
|
|
||||||
property string linkSpeed: ""
|
|
||||||
property string downloadSpeed: ""
|
property string downloadSpeed: ""
|
||||||
property string uploadSpeed: ""
|
property string uploadSpeed: ""
|
||||||
property string ping: ""
|
property string ping: ""
|
||||||
property bool internetAvailable: false
|
|
||||||
|
property var downloadHistory: []
|
||||||
|
property var uploadHistory: []
|
||||||
|
property var pingHistory: []
|
||||||
|
property int _maxHistory: 60
|
||||||
|
property bool internetAvailable: Networking.connectivity === NetworkConnectivity.Full
|
||||||
property bool vpnConnected: false
|
property bool vpnConnected: false
|
||||||
|
property bool vpnExists: false
|
||||||
property string vpnName: ""
|
property string vpnName: ""
|
||||||
property string vpnIp: ""
|
property string vpnIp: ""
|
||||||
property bool tailscaleConnected: false
|
property bool tailscaleConnected: false
|
||||||
|
property bool tailscaleExists: false
|
||||||
property string tailscaleIp: ""
|
property string tailscaleIp: ""
|
||||||
|
|
||||||
property bool running: false
|
property bool running: false
|
||||||
|
|
||||||
property string _activeDevice: ""
|
|
||||||
property real _prevRxBytes: 0
|
property real _prevRxBytes: 0
|
||||||
property real _prevTxBytes: 0
|
property real _prevTxBytes: 0
|
||||||
property var _prevUpdateTime: 0
|
property var _prevUpdateTime: 0
|
||||||
|
property string _trafficActiveDevice: ""
|
||||||
|
|
||||||
|
readonly property var networking: Networking
|
||||||
|
|
||||||
|
function findAllConnectedDevices() {
|
||||||
|
var devs = networking.devices.values
|
||||||
|
var result = []
|
||||||
|
for (var i = 0; i < devs.length; i++) {
|
||||||
|
if (devs[i].connected) result.push(devs[i])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
function getNmConnectionName(net) {
|
||||||
|
if (!net) return ""
|
||||||
|
var settings = net.nmSettings
|
||||||
|
if (settings.length > 0) return settings[0].id
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
property var _pendingDevices: []
|
||||||
|
|
||||||
|
function updateAll() {
|
||||||
|
var connected = findAllConnectedDevices()
|
||||||
|
_pendingDevices = connected
|
||||||
|
|
||||||
|
activeConnections.clear()
|
||||||
|
var hasWifi = false
|
||||||
|
var hasWired = false
|
||||||
|
var bestWifiSignal = 0
|
||||||
|
var trafficDev = connected.length > 0 ? connected[0] : null
|
||||||
|
|
||||||
|
for (var i = 0; i < connected.length; i++) {
|
||||||
|
var dev = connected[i]
|
||||||
|
var entry = {
|
||||||
|
interfaceName: dev.name,
|
||||||
|
interfaceType: "",
|
||||||
|
connectionName: "",
|
||||||
|
wifiName: "",
|
||||||
|
signalStrength: 0,
|
||||||
|
linkSpeed: "",
|
||||||
|
ipv4: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dev.type === DeviceType.Wifi) {
|
||||||
|
entry.interfaceType = "wifi"
|
||||||
|
hasWifi = true
|
||||||
|
var networks = dev.networks.values
|
||||||
|
for (var j = 0; j < networks.length; j++) {
|
||||||
|
var net = networks[j]
|
||||||
|
if (net.connected) {
|
||||||
|
entry.wifiName = net.name
|
||||||
|
entry.signalStrength = Math.round(net.signalStrength * 100)
|
||||||
|
entry.connectionName = root.getNmConnectionName(net)
|
||||||
|
if (net.signalStrength > bestWifiSignal) bestWifiSignal = net.signalStrength
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (dev.type === DeviceType.Wired) {
|
||||||
|
entry.interfaceType = "ethernet"
|
||||||
|
hasWired = true
|
||||||
|
entry.linkSpeed = dev.linkSpeed + " Mbps"
|
||||||
|
if (dev.network) {
|
||||||
|
entry.connectionName = root.getNmConnectionName(dev.network)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
activeConnections.append(entry)
|
||||||
|
|
||||||
|
if (dev.type === DeviceType.Wired && trafficDev !== dev) {
|
||||||
|
trafficDev = dev
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (trafficDev) {
|
||||||
|
_trafficActiveDevice = trafficDev.name
|
||||||
|
trafficScript.running = true
|
||||||
|
}
|
||||||
|
|
||||||
|
root.computeStatus(hasWired, hasWifi, bestWifiSignal)
|
||||||
|
|
||||||
|
if (connected.length > 0) {
|
||||||
|
ipv4Script.running = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
property Timer networkTimer: Timer {
|
property Timer networkTimer: Timer {
|
||||||
interval: 10000
|
interval: 10000
|
||||||
repeat: true
|
repeat: true
|
||||||
running: true
|
running: true
|
||||||
onTriggered: networkScript.running = true
|
onTriggered: root.updateAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
property Process networkScript: Process {
|
property Process ipv4Script: Process {
|
||||||
|
command: ["sh", "-c",
|
||||||
|
"ip -4 addr show 2>/dev/null | awk '/^[0-9]+:/{iface=$2; gsub(/:.*/, \"\", iface)} /inet /{print iface\" \"$2}' | " +
|
||||||
|
"grep -v '\\blo\\b' | grep -v '\\bvirbr' | grep -v '\\btailscale' | grep -v '\\bdocker' | grep -v '\\bveth' | " +
|
||||||
|
"sed 's|/[0-9]*||'"
|
||||||
|
]
|
||||||
|
stdout: StdioCollector {
|
||||||
|
onStreamFinished: root.parseIpv4Output(text.trim())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseIpv4Output(output) {
|
||||||
|
var ipMap = {}
|
||||||
|
if (!output) {
|
||||||
|
root.applyIpv4Map(ipMap)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var lines = output.split("\n")
|
||||||
|
for (var i = 0; i < lines.length; i++) {
|
||||||
|
var parts = lines[i].trim().split(" ")
|
||||||
|
if (parts.length >= 2) {
|
||||||
|
ipMap[parts[0]] = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root.applyIpv4Map(ipMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyIpv4Map(ipMap) {
|
||||||
|
for (var i = 0; i < activeConnections.count; i++) {
|
||||||
|
var iface = activeConnections.get(i).interfaceName
|
||||||
|
var ip = ipMap[iface] || ""
|
||||||
|
activeConnections.setProperty(i, "ipv4", ip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
property Process trafficScript: Process {
|
||||||
command: ["sh", "-c",
|
command: ["sh", "-c",
|
||||||
"echo '===SHOW==='; " +
|
|
||||||
"ACTIVE=$(nmcli -t device status 2>/dev/null | awk -F: '$3==\"connected\"{print $1; exit}'); " +
|
|
||||||
"echo \"ACTIVE_DEVICE=$ACTIVE\"; " +
|
|
||||||
"if [ -n \"$ACTIVE\" ]; then " +
|
|
||||||
" nmcli -t -f ALL device show \"$ACTIVE\" 2>/dev/null; " +
|
|
||||||
"fi; " +
|
|
||||||
"echo '===TRAFFIC==='; " +
|
"echo '===TRAFFIC==='; " +
|
||||||
"if [ -n \"$ACTIVE\" ]; then " +
|
"DEV=\"" + root._trafficActiveDevice + "\"; " +
|
||||||
" echo \"RX_BYTES=$(cat /sys/class/net/$ACTIVE/statistics/rx_bytes 2>/dev/null || echo 0)\"; " +
|
"if [ -n \"$DEV\" ]; then " +
|
||||||
" echo \"TX_BYTES=$(cat /sys/class/net/$ACTIVE/statistics/tx_bytes 2>/dev/null || echo 0)\"; " +
|
" 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; " +
|
"fi; " +
|
||||||
"echo '===WIFI==='; " +
|
|
||||||
"nmcli -t -f active,ssid,signal,chan,freq,rate dev wifi | awk -F: '$1==\"yes\" { $1=\"\"; sub(/^:/,\"\"); print }'; " +
|
|
||||||
"echo '===PING==='; " +
|
"echo '===PING==='; " +
|
||||||
"ping -c 1 -W 2 1.1.1.1 2>/dev/null | awk -F/ '/rtt/{print $5}' || echo ''; " +
|
"ping -c 1 -W 2 1.1.1.1 2>/dev/null | awk -F/ '/rtt/{print $5}' || echo ''; " +
|
||||||
"echo '===INTERNET==='; " +
|
"echo '===VPN_ALL==='; " +
|
||||||
"ping -c 1 -W 1 1.1.1.1 2>/dev/null >/dev/null && echo 1 || echo 0; " +
|
"nmcli -t -f NAME,TYPE connection show 2>/dev/null | grep -i vpn || true; " +
|
||||||
"echo '===VPN==='; " +
|
"echo '===VPN_ACTIVE==='; " +
|
||||||
"nmcli -t -f NAME,TYPE connection show --active 2>/dev/null | grep -i vpn || true; " +
|
"nmcli -t -f NAME,TYPE connection show --active 2>/dev/null | grep -i vpn || true; " +
|
||||||
"echo '===TAILSCALE==='; " +
|
"echo '===TAILSCALE_EXISTS==='; " +
|
||||||
"TAIL_IP=$(ip -4 addr show tailscale0 2>/dev/null | grep -oP 'inet \\K[\\d.]+'); " +
|
"ip link show tailscale0 >/dev/null 2>&1 && echo EXISTS || echo ''; " +
|
||||||
"if [ -n \"$TAIL_IP\" ]; then echo \"TAILSCALE_IP=$TAIL_IP\"; else echo \"\"; fi"
|
"echo '===TAILSCALE_IP==='; " +
|
||||||
|
"ip -4 addr show tailscale0 2>/dev/null | grep -oP 'inet \\K[\\d.]+' || echo ''"
|
||||||
]
|
]
|
||||||
|
|
||||||
stdout: StdioCollector {
|
stdout: StdioCollector {
|
||||||
onStreamFinished: root.parseNetworkOutput(text.trim())
|
onStreamFinished: root.parseOutput(text.trim())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function computeStatus(hasWired, hasWifi, bestSignal) {
|
||||||
|
if (!hasWired && !hasWifi) {
|
||||||
|
root.status = "/"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var parts = []
|
||||||
|
if (hasWired) parts.push("")
|
||||||
|
if (hasWifi) {
|
||||||
|
var sig = Math.round(bestSignal * 100)
|
||||||
|
if (sig > 80) parts.push("")
|
||||||
|
else if (sig > 60) parts.push("")
|
||||||
|
else if (sig > 40) parts.push("")
|
||||||
|
else if (sig > 20) parts.push("")
|
||||||
|
else if (sig > 0) parts.push("")
|
||||||
|
else parts.push("")
|
||||||
|
}
|
||||||
|
|
||||||
|
root.status = parts.join(" ")
|
||||||
|
}
|
||||||
|
|
||||||
function formatSpeed(bytesPerSec) {
|
function formatSpeed(bytesPerSec) {
|
||||||
if (bytesPerSec >= 1073741824) return (bytesPerSec / 1073741824).toFixed(1) + " GB/s"
|
if (bytesPerSec >= 1073741824) return (bytesPerSec / 1073741824).toFixed(1) + " GB/s"
|
||||||
if (bytesPerSec >= 1048576) return (bytesPerSec / 1048576).toFixed(1) + " MB/s"
|
if (bytesPerSec >= 1048576) return (bytesPerSec / 1048576).toFixed(1) + " MB/s"
|
||||||
@@ -80,54 +214,38 @@ QtObject {
|
|||||||
return bytesPerSec.toFixed(0) + " B/s"
|
return bytesPerSec.toFixed(0) + " B/s"
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNetworkOutput(output) {
|
function parseOutput(output) {
|
||||||
var sections = output.split("===TAILSCALE===")
|
var sections = output.split("===TAILSCALE_IP===")
|
||||||
var tailscaleSection = sections.length > 1 ? sections[1].trim() : ""
|
var tailscaleIpSection = sections.length > 1 ? sections[1].trim() : ""
|
||||||
|
|
||||||
var beforeTailscale = sections[0]
|
var beforeTsIp = sections[0]
|
||||||
sections = beforeTailscale.split("===VPN===")
|
sections = beforeTsIp.split("===TAILSCALE_EXISTS===")
|
||||||
var vpnSection = sections.length > 1 ? sections[1].trim() : ""
|
var tailscaleExistsSection = sections.length > 1 ? sections[1].trim() : ""
|
||||||
|
|
||||||
var beforeVpn = sections[0]
|
var beforeTsExist = sections[0]
|
||||||
sections = beforeVpn.split("===INTERNET===")
|
sections = beforeTsExist.split("===VPN_ACTIVE===")
|
||||||
root.internetAvailable = sections.length > 1 ? sections[1].trim() === "1" : false
|
var vpnActiveSection = sections.length > 1 ? sections[1].trim() : ""
|
||||||
|
|
||||||
var beforeInternet = sections[0]
|
var beforeVpnActive = sections[0]
|
||||||
sections = beforeInternet.split("===PING===")
|
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() : ""
|
root.ping = sections.length > 1 ? sections[1].trim() : ""
|
||||||
|
|
||||||
var beforePing = sections[0]
|
var pingVal = parseFloat(root.ping)
|
||||||
sections = beforePing.split("===WIFI===")
|
if (!isNaN(pingVal) && pingVal > 0) {
|
||||||
var wifiSection = sections.length > 1 ? sections[1].trim() : ""
|
var pingHist = root.pingHistory.slice()
|
||||||
|
pingHist.push(pingVal)
|
||||||
|
if (pingHist.length > root._maxHistory) pingHist.shift()
|
||||||
|
root.pingHistory = pingHist
|
||||||
|
}
|
||||||
|
|
||||||
var beforeWifi = sections[0]
|
var beforePing = sections[0]
|
||||||
sections = beforeWifi.split("===TRAFFIC===")
|
sections = beforePing.split("===TRAFFIC===")
|
||||||
var trafficSection = sections.length > 1 ? sections[1].trim() : ""
|
var trafficSection = sections.length > 1 ? sections[1].trim() : ""
|
||||||
|
|
||||||
var beforeTraffic = sections[0]
|
|
||||||
sections = beforeTraffic.split("===SHOW===")
|
|
||||||
var showSection = sections.length > 1 ? sections[1].trim() : ""
|
|
||||||
|
|
||||||
// Parse SHOW section
|
|
||||||
var activeDevice = ""
|
|
||||||
var lines = showSection.split("\n")
|
|
||||||
for (var i = 0; i < lines.length; i++) {
|
|
||||||
var line = lines[i].trim()
|
|
||||||
if (line.indexOf("ACTIVE_DEVICE=") === 0) {
|
|
||||||
activeDevice = line.substring("ACTIVE_DEVICE=".length)
|
|
||||||
} else if (line.indexOf("GENERAL.DEVICE:") === 0) {
|
|
||||||
root.interfaceName = line.substring("GENERAL.DEVICE:".length)
|
|
||||||
} else if (line.indexOf("GENERAL.TYPE:") === 0) {
|
|
||||||
root.interfaceType = line.substring("GENERAL.TYPE:".length)
|
|
||||||
} else if (line.indexOf("GENERAL.CONNECTION:") === 0) {
|
|
||||||
root.connectionName = line.substring("GENERAL.CONNECTION:".length)
|
|
||||||
} else if (line.indexOf("IP4.ADDRESS") >= 0) {
|
|
||||||
var ipPart = line.substring(line.indexOf(":") + 1)
|
|
||||||
root.ipv4 = ipPart.split("/")[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse TRAFFIC section
|
|
||||||
if (trafficSection) {
|
if (trafficSection) {
|
||||||
var trafficLines = trafficSection.split("\n")
|
var trafficLines = trafficSection.split("\n")
|
||||||
var rxBytes = 0
|
var rxBytes = 0
|
||||||
@@ -138,75 +256,41 @@ QtObject {
|
|||||||
else if (tline.indexOf("TX_BYTES=") === 0) txBytes = parseFloat(tline.substring("TX_BYTES=".length))
|
else if (tline.indexOf("TX_BYTES=") === 0) txBytes = parseFloat(tline.substring("TX_BYTES=".length))
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log("[Network] rxBytes:", rxBytes, "txBytes:", txBytes)
|
if (root._prevRxBytes > 0 && root._prevUpdateTime > 0) {
|
||||||
// console.log("[Network] activeDevice:", activeDevice, "| prevActiveDevice:", root._activeDevice)
|
|
||||||
// console.log("[Network] prevRxBytes:", root._prevRxBytes, "prevTxBytes:", root._prevTxBytes, "prevTime:", root._prevUpdateTime)
|
|
||||||
|
|
||||||
if (activeDevice === root._activeDevice && root._prevRxBytes > 0 && root._prevUpdateTime > 0) {
|
|
||||||
var now = Date.now()
|
var now = Date.now()
|
||||||
var interval = (now - root._prevUpdateTime) / 1000
|
var interval = (now - root._prevUpdateTime) / 1000
|
||||||
// console.log("[Network] Same device, interval:", interval, "s")
|
|
||||||
// console.log("[Network] rxDelta:", (rxBytes - root._prevRxBytes), "txDelta:", (txBytes - root._prevTxBytes))
|
|
||||||
if (interval > 0) {
|
if (interval > 0) {
|
||||||
var downBps = (rxBytes - root._prevRxBytes) / interval
|
var dlBytes = (rxBytes - root._prevRxBytes) / interval
|
||||||
var upBps = (txBytes - root._prevTxBytes) / interval
|
var ulBytes = (txBytes - root._prevTxBytes) / interval
|
||||||
// console.log("[Network] downBps:", downBps, "upBps:", upBps)
|
root.downloadSpeed = root.formatSpeed(dlBytes)
|
||||||
root.downloadSpeed = root.formatSpeed(downBps)
|
root.uploadSpeed = root.formatSpeed(ulBytes)
|
||||||
root.uploadSpeed = root.formatSpeed(upBps)
|
|
||||||
// console.log("[Network] downloadSpeed:", root.downloadSpeed, "uploadSpeed:", root.uploadSpeed)
|
var dlHist = root.downloadHistory.slice()
|
||||||
} else {
|
dlHist.push(dlBytes)
|
||||||
console.log("[Network] Interval zero or negative, skipping")
|
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 {
|
} else {
|
||||||
// console.log("[Network] First run or device changed, storing baseline")
|
|
||||||
root.downloadSpeed = ""
|
root.downloadSpeed = ""
|
||||||
root.uploadSpeed = ""
|
root.uploadSpeed = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
root._activeDevice = activeDevice
|
|
||||||
root._prevRxBytes = rxBytes
|
root._prevRxBytes = rxBytes
|
||||||
root._prevTxBytes = txBytes
|
root._prevTxBytes = txBytes
|
||||||
root._prevUpdateTime = Date.now()
|
root._prevUpdateTime = Date.now()
|
||||||
// console.log("[Network] Stored baseline: rxBytes:", root._prevRxBytes, "txBytes:", root._prevTxBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse WIFI section
|
root.vpnExists = vpnAllSection.length > 0
|
||||||
if (wifiSection) {
|
if (vpnActiveSection) {
|
||||||
var wifiParts = wifiSection.split(" ")
|
|
||||||
if (wifiParts.length >= 2) {
|
|
||||||
root.wifiName = wifiParts[0] || ""
|
|
||||||
root.signalStrength = wifiParts[1] || ""
|
|
||||||
if (wifiParts.length >= 3) root.wifiChannel = wifiParts[2] || ""
|
|
||||||
if (wifiParts.length >= 4) root.wifiFrequency = wifiParts[3] + " " + wifiParts[4] || ""
|
|
||||||
if (wifiParts.length >= 5) root.linkSpeed = wifiParts[5] + " " + wifiParts[6] || ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Determine status icon
|
|
||||||
if (root.interfaceType === "ethernet") {
|
|
||||||
root.status = ""
|
|
||||||
} else if (root.interfaceType === "wifi") {
|
|
||||||
if (!root.connectionName) {
|
|
||||||
root.status = ""
|
|
||||||
} else {
|
|
||||||
var sig = parseInt(root.signalStrength)
|
|
||||||
if (sig > 80) root.status = ""
|
|
||||||
else if (sig > 60) root.status = ""
|
|
||||||
else if (sig > 40) root.status = ""
|
|
||||||
else if (sig > 20) root.status = ""
|
|
||||||
else root.status = ""
|
|
||||||
}
|
|
||||||
} else if (!activeDevice) {
|
|
||||||
root.status = "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse VPN section
|
|
||||||
if (vpnSection) {
|
|
||||||
root.vpnConnected = true
|
root.vpnConnected = true
|
||||||
var vpnLines = vpnSection.split("\n")
|
var vpnLines = vpnActiveSection.split("\n")
|
||||||
if (vpnLines.length > 0) {
|
if (vpnLines.length > 0) {
|
||||||
var vpnParts = vpnLines[0].split(":")
|
root.vpnName = vpnLines[0].split(":")[0] || ""
|
||||||
root.vpnName = vpnParts[0] || ""
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
root.vpnConnected = false
|
root.vpnConnected = false
|
||||||
@@ -214,10 +298,10 @@ QtObject {
|
|||||||
root.vpnIp = ""
|
root.vpnIp = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse TAILSCALE section
|
root.tailscaleExists = tailscaleExistsSection.indexOf("EXISTS") >= 0
|
||||||
if (tailscaleSection && tailscaleSection.indexOf("TAILSCALE_IP=") === 0) {
|
if (tailscaleIpSection) {
|
||||||
root.tailscaleConnected = true
|
root.tailscaleConnected = true
|
||||||
root.tailscaleIp = tailscaleSection.substring("TAILSCALE_IP=".length)
|
root.tailscaleIp = tailscaleIpSection
|
||||||
} else {
|
} else {
|
||||||
root.tailscaleConnected = false
|
root.tailscaleConnected = false
|
||||||
root.tailscaleIp = ""
|
root.tailscaleIp = ""
|
||||||
@@ -225,6 +309,6 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
networkScript.running = true
|
root.updateAll()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+82
-108
@@ -1,5 +1,4 @@
|
|||||||
import QtQuick
|
import QtQuick
|
||||||
import Quickshell
|
|
||||||
import "../../../config"
|
import "../../../config"
|
||||||
import "../states/Network"
|
import "../states/Network"
|
||||||
|
|
||||||
@@ -12,9 +11,8 @@ MouseArea {
|
|||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
console.log("[Network] Refreshing... current status: " + NetworkState.status)
|
console.log("[Network] Manual refresh")
|
||||||
NetworkState.networkScript.running = false
|
NetworkState.updateAll()
|
||||||
NetworkState.networkScript.running = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
@@ -44,166 +42,142 @@ MouseArea {
|
|||||||
|
|
||||||
Column {
|
Column {
|
||||||
spacing: parent.spacing
|
spacing: parent.spacing
|
||||||
|
|
||||||
|
Repeater {
|
||||||
|
model: NetworkState.activeConnections
|
||||||
|
delegate: Column {
|
||||||
|
spacing: 2
|
||||||
Text {
|
Text {
|
||||||
text: "interfaceName: " + NetworkState.interfaceName
|
text: model.interfaceType === "wifi" ? "Wifi" : "Ethernet"
|
||||||
wrapMode: Text.WordWrap
|
color: Colors.secondary
|
||||||
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
|
font.family: Config.font
|
||||||
|
font.bold: true
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: " interface: " + model.interfaceName
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "interfaceType: " + NetworkState.interfaceType
|
text: " connection: " + model.connectionName
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "connectionName: " + NetworkState.connectionName
|
text: " ipv4: " + model.ipv4
|
||||||
wrapMode: Text.WordWrap
|
color: Colors.primary
|
||||||
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
|
font.family: Config.font
|
||||||
|
visible: model.ipv4 !== ""
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: " wifi: " + model.wifiName + " signal: " + model.signalStrength + "%"
|
||||||
|
color: Colors.primary
|
||||||
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
|
font.family: Config.font
|
||||||
|
visible: model.interfaceType === "wifi"
|
||||||
|
}
|
||||||
|
Text {
|
||||||
|
text: " linkSpeed: " + model.linkSpeed
|
||||||
|
color: Colors.primary
|
||||||
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
|
font.family: Config.font
|
||||||
|
visible: model.interfaceType === "ethernet"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Item { width: 1; height: parent.spacing }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 2
|
||||||
|
Text {
|
||||||
|
text: "download: " + NetworkState.downloadSpeed
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
}
|
}
|
||||||
|
Sparkline {
|
||||||
|
values: NetworkState.downloadHistory
|
||||||
|
lineColor: Colors.primary
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 2
|
||||||
Text {
|
Text {
|
||||||
text: "ipv4: " + NetworkState.ipv4
|
text: "upload: " + NetworkState.uploadSpeed
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
}
|
}
|
||||||
|
Sparkline {
|
||||||
|
values: NetworkState.uploadHistory
|
||||||
|
lineColor: Colors.tertiary
|
||||||
|
width: parent.width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
width: parent.width
|
||||||
|
spacing: 2
|
||||||
Text {
|
Text {
|
||||||
text: "wifiName: " + NetworkState.wifiName
|
text: "ping: " + NetworkState.ping + " ms"
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
}
|
}
|
||||||
|
Sparkline {
|
||||||
Text {
|
values: NetworkState.pingHistory
|
||||||
text: "signalStrength: " + NetworkState.signalStrength
|
lineColor: Colors.error
|
||||||
wrapMode: Text.WordWrap
|
width: parent.width
|
||||||
color: Colors.primary
|
}
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "wifiChannel: " + NetworkState.wifiChannel
|
text: "no connection"
|
||||||
wrapMode: Text.WordWrap
|
color: Colors.error
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
|
visible: !NetworkState.internetAvailable
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "wifiFrequency: " + NetworkState.wifiFrequency
|
text: "VPN: disconnected"
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
|
visible: NetworkState.vpnExists && !NetworkState.vpnConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "linkSpeed: " + NetworkState.linkSpeed
|
text: "VPN: " + NetworkState.vpnName
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
|
visible: NetworkState.vpnConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "downloadSpeed: " + NetworkState.downloadSpeed
|
text: "Tailscale: disconnected"
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
|
visible: NetworkState.tailscaleExists && !NetworkState.tailscaleConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
text: "uploadSpeed: " + NetworkState.uploadSpeed
|
text: "Tailscale: " + NetworkState.tailscaleIp
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
color: Colors.primary
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "ping: " + NetworkState.ping
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "internetAvailable: " + NetworkState.internetAvailable
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "vpnConnected: " + NetworkState.vpnConnected
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "vpnName: " + NetworkState.vpnName
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "vpnIp: " + NetworkState.vpnIp
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "tailscaleConnected: " + NetworkState.tailscaleConnected
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
|
||||||
font.family: Config.font
|
|
||||||
}
|
|
||||||
|
|
||||||
Text {
|
|
||||||
text: "tailscaleIp: " + NetworkState.tailscaleIp
|
|
||||||
wrapMode: Text.WordWrap
|
|
||||||
color: Colors.primary
|
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
|
||||||
font.pixelSize: Config.font_size_tiny(bar.height)
|
font.pixelSize: Config.font_size_tiny(bar.height)
|
||||||
font.family: Config.font
|
font.family: Config.font
|
||||||
|
visible: NetworkState.tailscaleConnected
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import QtQuick
|
||||||
|
import "../../../config"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var values: []
|
||||||
|
property color lineColor: Colors.primary
|
||||||
|
property color fillColor: Qt.alpha(Colors.primary, 0.15)
|
||||||
|
property int pointCount: 60
|
||||||
|
property real minWidth: 120
|
||||||
|
property real graphHeight: 30
|
||||||
|
|
||||||
|
width: Math.max(minWidth, parent ? parent.width : minWidth)
|
||||||
|
height: graphHeight
|
||||||
|
|
||||||
|
Canvas {
|
||||||
|
id: canvas
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onPaint: {
|
||||||
|
var ctx = getContext("2d")
|
||||||
|
ctx.clearRect(0, 0, width, height)
|
||||||
|
|
||||||
|
var data = root.values
|
||||||
|
if (!data || data.length < 2) return
|
||||||
|
|
||||||
|
var displayCount = Math.min(data.length, root.pointCount)
|
||||||
|
var startIdx = data.length - displayCount
|
||||||
|
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 drawH = height - padY * 2
|
||||||
|
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.moveTo(0, height - padY - (slice[0] / maxVal) * drawH)
|
||||||
|
for (var j = 1; j < slice.length; j++) {
|
||||||
|
var x = j * stepX
|
||||||
|
var y = height - padY - (slice[j] / maxVal) * drawH
|
||||||
|
ctx.lineTo(x, y)
|
||||||
|
}
|
||||||
|
ctx.strokeStyle = root.lineColor
|
||||||
|
ctx.lineWidth = 1.5
|
||||||
|
ctx.stroke()
|
||||||
|
|
||||||
|
ctx.lineTo(width, height)
|
||||||
|
ctx.lineTo(0, height)
|
||||||
|
ctx.closePath()
|
||||||
|
ctx.fillStyle = root.fillColor
|
||||||
|
ctx.fill()
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: root
|
||||||
|
function onValuesChanged() { canvas.requestPaint() }
|
||||||
|
function onWidthChanged() { canvas.requestPaint() }
|
||||||
|
function onHeightChanged() { canvas.requestPaint() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user