1
1
Fork 1
mirror of https://github.com/oddlama/nix-config.git synced 2025-10-11 07:10:39 +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) { optional (interfaceName != null) {
${interfaceName} = { ${interfaceName} = {
mac = network.matchConfig.MACAddress or null; mac = network.matchConfig.MACAddress or null;
# TODO: FIXME: remove cidr mask
addresses = network.address ++ (network.networkConfig.Address or []); addresses = network.address ++ (network.networkConfig.Address or []);
gateways = network.gateway ++ (network.networkConfig.Gateway or []); gateways = network.gateway ++ (network.networkConfig.Gateway or []);
}; };

View file

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

View file

@ -5,6 +5,9 @@ f: {
}: let }: let
inherit inherit
(lib) (lib)
attrValues
flatten
flip
mkOption mkOption
types types
; ;
@ -28,6 +31,12 @@ in
default = "Unnamed network '${networkSubmod.config.id}'"; 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 { color = mkOption {
description = "The color of this network"; description = "The color of this network";
default = "random"; 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 = { hardware = {
description = mkOption { description = mkOption {
description = "A description of this node's hardware. Usually the device name or lost of the most important components."; description = "A description of this node's hardware. Usually the model name or a description the most important components.";
type = types.lines; type = types.str;
default = ""; default = "";
}; };

View file

@ -55,74 +55,145 @@
else value else value
); );
netToElk = net: { mkEdge = from: to: extra: {
children."net:${net.id}" = { edges."${from}__${to}" =
width = 50; {
height = 50; sources = [from];
}; targets = [to];
}
// extra;
}; };
idForInterface = node: interfaceId: mkLabel = text: scale: extraStyle: {
if (node.preferredRenderType == "card") height = scale * 12;
then "children.node:${node.id}.ports.interface:${interfaceId}" width = scale * 7.3 * (stringLength text);
else "children.node:${node.id}"; 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: nodeInterfaceToElk = node: interface:
[ [
(optionalAttrs (node.preferredRenderType == "card") { # Interface for node in main view
{
children."node:${node.id}".ports."interface:${interface.id}" = { children."node:${node.id}".ports."interface:${interface.id}" = {
properties."port.side" = "WEST"; properties = optionalAttrs (node.preferredRenderType == "card") {
"port.side" = "WEST";
};
width = 8; width = 8;
height = 8; height = 8;
style.stroke = "#70a5eb"; style.stroke = "#70a5eb";
style.fill = "#74bee9"; style.fill = "#74bee9";
labels.name = { labels =
height = 12; {
width = 7.5 * (stringLength interface.id); "00-name" = mkLabel interface.id 1 {};
text = interface.id; }
}; // 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: ++ optionals (!interface.virtual) (flip map interface.physicalConnections (
optionalAttrs ( conn:
(!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${x.node}.interfaces.${x.interface}.physicalConnections) optionalAttrs (
|| (node.id < x.node) (!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${conn.node}.interfaces.${conn.interface}.physicalConnections)
) { || (node.id < conn.node)
edges."node:${node.id}.ports.interface:${interface.id}-to-node:${x.node}.ports.interface:${x.interface}" = { ) (
sources = [(idForInterface node interface.id)]; # Edge in main view
targets = [(idForInterface config.nodes.${x.node} x.interface)]; mkEdge
}; (idForInterface node interface.id)
})); (idForInterface config.nodes.${conn.node} conn.interface)
{}
)
));
nodeToElk = node: nodeToElk = node:
[ [
# Add node to main view
{ {
children."node:${node.id}" = { children."node:${node.id}" = {
svg = { svg = {
file = config.lib.renderers.svg.node.mkPreferredRender node; file = config.lib.renderers.svg.node.mkPreferredRender node;
scale = 0.8; 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"; properties."portLabels.placement" = "OUTSIDE";
}; };
} }
] ]
++ optional (node.parent != null) { ++ optional (node.parent != null) (
children."node:${node.parent}".ports.guests = { {
properties."port.side" = "EAST"; children."node:${node.parent}".ports.guests = {
width = 8; properties."port.side" = "EAST";
height = 8; width = 8;
style.stroke = "#49d18d"; height = 8;
style.fill = "#78dba9"; style.stroke = "#49d18d";
}; style.fill = "#78dba9";
edges."node:${node.parent}.ports.guests-to-node:${node.id}" = { labels."00-name" = mkLabel "guests" 1 {};
sources = ["children.node:${node.parent}.ports.guests"]; };
targets = ["children.node:${node.id}"]; }
// mkEdge "children.node:${node.parent}.ports.guests" "children.node:${node.id}" {
style.stroke-dasharray = "10,8"; style.stroke-dasharray = "10,8";
style.stroke-linecap = "round"; style.stroke-linecap = "round";
}; }
} )
++ map (nodeInterfaceToElk node) (attrValues node.interfaces); ++ map (nodeInterfaceToElk node) (attrValues node.interfaces);
in { in {
options.renderers.elk = { 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 # Add service overview
{ {
children.services-overview = { children.services-overview = {

View file

@ -1,4 +1,13 @@
# TODO: # 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 # - ip labels on edges
# - network centric view # - network centric view
# - better layout for interfaces in svg # - better layout for interfaces in svg
@ -106,6 +115,28 @@
<div tw="flex mt-2"></div> <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 { node = rec {
mkInterface = interface: let mkInterface = interface: let
color = color =
@ -193,23 +224,6 @@
</div> </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 mkCard = node: let
services = filter (x: !x.hidden) (attrValues node.services); services = filter (x: !x.hidden) (attrValues node.services);
guests = filter (x: x.parent == node.id) (attrValues config.nodes); guests = filter (x: x.parent == node.id) (attrValues config.nodes);
@ -314,8 +328,10 @@ in {
# FIXME: networks.mkOverview = renderHtmlToSvg html.networks.mkOverview "networks-overview"; # FIXME: networks.mkOverview = renderHtmlToSvg html.networks.mkOverview "networks-overview";
services.mkOverview = renderHtmlToSvg html.services.mkOverview "services-overview"; services.mkOverview = renderHtmlToSvg html.services.mkOverview "services-overview";
net.mkCard = net: renderHtmlToSvg (html.net.mkCard net) "card-net-${net.id}";
node = { 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}"; mkCard = node: renderHtmlToSvg (html.node.mkCard node) "card-node-${node.id}";
mkPreferredRender = node: renderHtmlToSvg (html.node.mkPreferredRender node) "preferred-render-node-${node.id}"; mkPreferredRender = node: renderHtmlToSvg (html.node.mkPreferredRender node) "preferred-render-node-${node.id}";
}; };