Own the network: details, VPN, enterprise Wi-Fi, and a firewall that can also allow
Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
// The facts about one connection that otherwise need a terminal.
|
||||
//
|
||||
// IP address, gateway, DNS and MAC are the four things people leave this app
|
||||
// for, and they were the reason the Connections page still pointed at GNOME.
|
||||
// They are read-only here: editing them properly means static addressing, which
|
||||
// is a page of its own rather than four fields smuggled into a details drawer.
|
||||
//
|
||||
// Nothing here is ever a secret. panama-network's `details` verb returns
|
||||
// addresses only -- no PSK, no enterprise password -- so this component can be
|
||||
// shown for any connection without deciding what is safe to draw.
|
||||
//
|
||||
// The values are set in the interface face with tabular figures rather than a
|
||||
// monospaced one. Theme bans monospaced text outright (fontMono is the icon
|
||||
// face, not a text face), and an address only needs its digits to line up.
|
||||
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
Column {
|
||||
id: root
|
||||
|
||||
// { ip4, gateway, dns: [], mac, macRandomized } as the helper reports it,
|
||||
// or null while the read has not come back. Null is NOT "no address": the
|
||||
// component says it is still reading rather than claiming an answer.
|
||||
property var details: null
|
||||
|
||||
// [{ key, value, note }] -- only the facts that actually have a value, so a
|
||||
// connection with no gateway shows three rows rather than a blank one.
|
||||
readonly property var facts: {
|
||||
const source = root.details;
|
||||
if (!source)
|
||||
return [];
|
||||
|
||||
const rows = [];
|
||||
const ip4 = String(source.ip4 ?? "");
|
||||
if (ip4 !== "")
|
||||
rows.push({ key: "IPv4 address", value: ip4, note: "" });
|
||||
|
||||
const ip6 = String(source.ip6 ?? "");
|
||||
if (ip6 !== "")
|
||||
rows.push({ key: "IPv6 address", value: ip6, note: "" });
|
||||
|
||||
const gateway = String(source.gateway ?? "");
|
||||
if (gateway !== "")
|
||||
rows.push({ key: "Gateway", value: gateway, note: "" });
|
||||
|
||||
const dns = Array.isArray(source.dns)
|
||||
? source.dns.map(entry => String(entry)).filter(entry => entry !== "")
|
||||
: [];
|
||||
if (dns.length > 0)
|
||||
rows.push({
|
||||
key: dns.length === 1 ? "DNS" : "DNS servers",
|
||||
value: dns.join(" · "),
|
||||
note: ""
|
||||
});
|
||||
|
||||
const mac = String(source.mac ?? "");
|
||||
if (mac !== "")
|
||||
rows.push({
|
||||
key: "MAC address",
|
||||
value: mac,
|
||||
note: source.macRandomized === true ? "randomized" : ""
|
||||
});
|
||||
|
||||
return rows;
|
||||
}
|
||||
|
||||
width: parent ? parent.width : 620
|
||||
spacing: 5
|
||||
topPadding: 8
|
||||
bottomPadding: 10
|
||||
|
||||
Repeater {
|
||||
model: root.facts
|
||||
|
||||
Item {
|
||||
id: fact
|
||||
|
||||
required property var modelData
|
||||
|
||||
width: parent.width
|
||||
implicitHeight: Math.max(key.implicitHeight, value.implicitHeight)
|
||||
|
||||
Text {
|
||||
id: key
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 132
|
||||
text: String(fact.modelData.key ?? "")
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
Text {
|
||||
id: value
|
||||
anchors.left: key.right
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
text: String(fact.modelData.value ?? "")
|
||||
+ (String(fact.modelData.note ?? "") !== ""
|
||||
? " · " + String(fact.modelData.note) : "")
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.features: Theme.tabularFigures
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Two honest empty states, and they say different things. "Still reading"
|
||||
// is not "no address", and a page that renders the first as the second is
|
||||
// how a working connection comes to look broken for half a second.
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: !root.details
|
||||
text: "Reading this connection's addresses…"
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: !!root.details && root.facts.length === 0
|
||||
text: "NetworkManager reports no addresses for this connection."
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
// The helper's own aside -- chiefly "this takes effect on the next
|
||||
// reconnect" after a MAC randomization change. It belongs to the answer,
|
||||
// so it is shown with the answer rather than guessed at by the page.
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: String(root.details?.note ?? "") !== ""
|
||||
topPadding: 4
|
||||
text: String(root.details?.note ?? "")
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user