1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-10 14:50:40 +02:00

feat(topology): add network-centric view and port labels

This commit is contained in:
oddlama 2024-03-29 20:13:34 +01:00
parent f24fd89ae5
commit ecaea9b906
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
6 changed files with 183 additions and 61 deletions

View file

@ -60,6 +60,7 @@ in {
optional (interfaceName != null) {
${interfaceName} = {
mac = network.matchConfig.MACAddress or null;
# TODO: FIXME: remove cidr mask
addresses = network.address ++ (network.networkConfig.Address or []);
gateways = network.gateway ++ (network.networkConfig.Gateway or []);
};

View file

@ -35,6 +35,7 @@ in {
in {
${networkId wgName} = {
name = mkDefault "Wireguard network '${wgName}'";
icon = "interfaces.wireguard";
cidrv4 = headOrNull (filter lib.net.ip.isv4 networkCidrs);
cidrv6 = headOrNull (filter lib.net.ip.isv6 networkCidrs);
};

View file

@ -5,6 +5,9 @@ f: {
}: let
inherit
(lib)
attrValues
flatten
flip
mkOption
types
;
@ -28,6 +31,12 @@ in
default = "Unnamed network '${networkSubmod.config.id}'";
};
icon = mkOption {
description = "The icon representing this network. Must be a path to an image or a valid icon name (<category>.<name>).";
type = types.nullOr (types.either types.path types.str);
default = null;
};
color = mkOption {
description = "The color of this network";
default = "random";
@ -53,4 +62,15 @@ in
};
}));
};
config = {
assertions = flatten (
flip map (attrValues config.networks) (
network: [
(config.lib.assertions.iconValid
network.icon "networks.${network.id}.icon")
]
)
);
};
}

View file

@ -42,8 +42,8 @@ in
hardware = {
description = mkOption {
description = "A description of this node's hardware. Usually the device name or lost of the most important components.";
type = types.lines;
description = "A description of this node's hardware. Usually the model name or a description the most important components.";
type = types.str;
default = "";
};

View file

@ -55,74 +55,145 @@
else value
);
netToElk = net: {
children."net:${net.id}" = {
width = 50;
height = 50;
};
mkEdge = from: to: extra: {
edges."${from}__${to}" =
{
sources = [from];
targets = [to];
}
// extra;
};
idForInterface = node: interfaceId:
if (node.preferredRenderType == "card")
then "children.node:${node.id}.ports.interface:${interfaceId}"
else "children.node:${node.id}";
mkLabel = text: scale: extraStyle: {
height = scale * 12;
width = scale * 7.3 * (stringLength text);
inherit text;
style =
{
style = "font: ${toString (scale * 12)}px JetBrains Mono;";
}
// extraStyle;
};
netToElk = net: [
{
children.network.children."net:${net.id}" = {
svg = {
file = config.lib.renderers.svg.net.mkCard net;
scale = 0.8;
};
properties."portLabels.placement" = "OUTSIDE";
};
}
];
idForInterface = node: interfaceId: "children.node:${node.id}.ports.interface:${interfaceId}";
nodeInterfaceToElk = node: interface:
[
(optionalAttrs (node.preferredRenderType == "card") {
# Interface for node in main view
{
children."node:${node.id}".ports."interface:${interface.id}" = {
properties."port.side" = "WEST";
properties = optionalAttrs (node.preferredRenderType == "card") {
"port.side" = "WEST";
};
width = 8;
height = 8;
style.stroke = "#70a5eb";
style.fill = "#74bee9";
labels.name = {
height = 12;
width = 7.5 * (stringLength interface.id);
text = interface.id;
};
labels =
{
"00-name" = mkLabel interface.id 1 {};
}
// optionalAttrs (interface.mac != null) {
"50-mac" = mkLabel interface.mac 1 {fill = "#70a5eb";};
};
};
})
}
# Interface for node in network-centric view
{
children.network.children."node:${node.id}".ports."interface:${interface.id}" = {
width = 8;
height = 8;
style.stroke = "#70a5eb";
style.fill = "#74bee9";
labels =
{
"00-name" = mkLabel interface.id 1 {};
}
// optionalAttrs (interface.mac != null) {
"50-mac" = mkLabel interface.mac 1 {fill = "#70a5eb";};
}
// optionalAttrs (interface.addresses != []) {
"60-addrs" = mkLabel (toString interface.addresses) 1 {fill = "#f9a872";};
};
};
}
# Edge in network-centric view
(optionalAttrs (interface.network != null) (
mkEdge ("children.network." + idForInterface node interface.id) "children.network.children.net:${interface.network}" {}
))
]
++ optionals (!interface.virtual) (flip map interface.physicalConnections (x:
optionalAttrs (
(!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${x.node}.interfaces.${x.interface}.physicalConnections)
|| (node.id < x.node)
) {
edges."node:${node.id}.ports.interface:${interface.id}-to-node:${x.node}.ports.interface:${x.interface}" = {
sources = [(idForInterface node interface.id)];
targets = [(idForInterface config.nodes.${x.node} x.interface)];
};
}));
++ optionals (!interface.virtual) (flip map interface.physicalConnections (
conn:
optionalAttrs (
(!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${conn.node}.interfaces.${conn.interface}.physicalConnections)
|| (node.id < conn.node)
) (
# Edge in main view
mkEdge
(idForInterface node interface.id)
(idForInterface config.nodes.${conn.node} conn.interface)
{}
)
));
nodeToElk = node:
[
# Add node to main view
{
children."node:${node.id}" = {
svg = {
file = config.lib.renderers.svg.node.mkPreferredRender node;
scale = 0.8;
};
properties."portConstraints" = "FIXED_SIDE";
properties =
{
"portLabels.placement" = "OUTSIDE";
}
// optionalAttrs (node.preferredRenderType == "card") {
"portConstraints" = "FIXED_SIDE";
};
};
}
# Add node to network-centric view
{
children.network.children."node:${node.id}" = {
svg = {
file = config.lib.renderers.svg.node.mkImageWithName node;
scale = 0.8;
};
properties."portLabels.placement" = "OUTSIDE";
};
}
]
++ optional (node.parent != null) {
children."node:${node.parent}".ports.guests = {
properties."port.side" = "EAST";
width = 8;
height = 8;
style.stroke = "#49d18d";
style.fill = "#78dba9";
};
edges."node:${node.parent}.ports.guests-to-node:${node.id}" = {
sources = ["children.node:${node.parent}.ports.guests"];
targets = ["children.node:${node.id}"];
++ optional (node.parent != null) (
{
children."node:${node.parent}".ports.guests = {
properties."port.side" = "EAST";
width = 8;
height = 8;
style.stroke = "#49d18d";
style.fill = "#78dba9";
labels."00-name" = mkLabel "guests" 1 {};
};
}
// mkEdge "children.node:${node.parent}.ports.guests" "children.node:${node.id}" {
style.stroke-dasharray = "10,8";
style.stroke-linecap = "round";
};
}
}
)
++ map (nodeInterfaceToElk node) (attrValues node.interfaces);
in {
options.renderers.elk = {
@ -151,6 +222,19 @@ in {
};
}
# Add network-centric section
{
children.network = {
style = {
stroke = "#101419";
stroke-width = 2;
fill = "#080a0d";
rx = 12;
};
labels."00-name" = mkLabel "Network View" 1 {};
};
}
# Add service overview
{
children.services-overview = {

View file

@ -1,4 +1,13 @@
# TODO:
# - systemd extractor remove cidr mask
# - address port label render make newline capable (multiple port labels)
# - mac address show!
# - split network layout or make rectpacking of childs
# - NAT indication
# - bottom hw image distorted in card view (move to top anyway)
# - embed font
# - network overview card (list all networks with name and cidr, legend style)
# - colors!
# - ip labels on edges
# - network centric view
# - better layout for interfaces in svg
@ -106,6 +115,28 @@
<div tw="flex mt-2"></div>
'';
net = rec {
mkCard = net: {
width = 480;
html =
mkCardContainer
/*
html
*/
''
<div tw="flex flex-row mx-6 mt-2 items-center">
${mkImageMaybe "w-8 h-8 mr-4" (config.lib.icons.get net.icon)}
<h2 tw="text-2xl font-bold">${net.name}</h2>
<div tw="flex grow min-w-8"></div>
<div tw="flex flex-none bg-[#ff0000] w-12 h-12 ml-4 rounded-lg"></div>
</div>
<div tw="flex flex-col mx-6 my-2 grow">
${optionalString (net.cidrv4 != null) ''<div tw="flex flex-row"><span tw="text-lg m-0"><b>CIDRv4</b></span><span tw="text-lg m-0 ml-4">${net.cidrv4}</span></div>''}
${optionalString (net.cidrv6 != null) ''<div tw="flex flex-row"><span tw="text-lg m-0"><b>CIDRv6</b></span><span tw="text-lg m-0 ml-4">${net.cidrv6}</span></div>''}
'';
};
};
node = rec {
mkInterface = interface: let
color =
@ -193,23 +224,6 @@
</div>
'';
mkNetCard = node: {
width = 480;
html =
mkCardContainer
/*
html
*/
''
${mkTitle node}
${concatLines (map mkInterface (attrValues node.interfaces))}
${optionalString (node.interfaces != {}) spacingMt2}
${mkImageMaybe "w-full h-24" node.hardware.image}
'';
};
mkCard = node: let
services = filter (x: !x.hidden) (attrValues node.services);
guests = filter (x: x.parent == node.id) (attrValues config.nodes);
@ -314,8 +328,10 @@ in {
# FIXME: networks.mkOverview = renderHtmlToSvg html.networks.mkOverview "networks-overview";
services.mkOverview = renderHtmlToSvg html.services.mkOverview "services-overview";
net.mkCard = net: renderHtmlToSvg (html.net.mkCard net) "card-net-${net.id}";
node = {
mkNetCard = node: renderHtmlToSvg (html.node.mkNetCard node) "card-network-${node.id}";
mkImageWithName = node: renderHtmlToSvg (html.node.mkImageWithName node) "image-with-name-${node.id}";
mkCard = node: renderHtmlToSvg (html.node.mkCard node) "card-node-${node.id}";
mkPreferredRender = node: renderHtmlToSvg (html.node.mkPreferredRender node) "preferred-render-node-${node.id}";
};