diff --git a/topology/nixos/extractors/systemd-network.nix b/topology/nixos/extractors/systemd-network.nix index 1fcca59..044a082 100644 --- a/topology/nixos/extractors/systemd-network.nix +++ b/topology/nixos/extractors/systemd-network.nix @@ -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 []); }; diff --git a/topology/nixos/extractors/wireguard.nix b/topology/nixos/extractors/wireguard.nix index fb04f14..1bb1774 100644 --- a/topology/nixos/extractors/wireguard.nix +++ b/topology/nixos/extractors/wireguard.nix @@ -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); }; diff --git a/topology/options/networks.nix b/topology/options/networks.nix index 9b72305..c5c1d81 100644 --- a/topology/options/networks.nix +++ b/topology/options/networks.nix @@ -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 (.)."; + 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") + ] + ) + ); + }; } diff --git a/topology/options/nodes.nix b/topology/options/nodes.nix index 53fde7b..1d2f5a4 100644 --- a/topology/options/nodes.nix +++ b/topology/options/nodes.nix @@ -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 = ""; }; diff --git a/topology/topology/renderers/elk/default.nix b/topology/topology/renderers/elk/default.nix index 923a93e..ba57f72 100644 --- a/topology/topology/renderers/elk/default.nix +++ b/topology/topology/renderers/elk/default.nix @@ -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 = { diff --git a/topology/topology/renderers/svg/default.nix b/topology/topology/renderers/svg/default.nix index dc0cfe9..8bcb456 100644 --- a/topology/topology/renderers/svg/default.nix +++ b/topology/topology/renderers/svg/default.nix @@ -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 @@
''; + net = rec { + mkCard = net: { + width = 480; + html = + mkCardContainer + /* + html + */ + '' +
+ ${mkImageMaybe "w-8 h-8 mr-4" (config.lib.icons.get net.icon)} +

${net.name}

+
+
+
+
+ ${optionalString (net.cidrv4 != null) ''
CIDRv4${net.cidrv4}
''} + ${optionalString (net.cidrv6 != null) ''
CIDRv6${net.cidrv6}
''} + ''; + }; + }; + node = rec { mkInterface = interface: let color = @@ -193,23 +224,6 @@
''; - 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}"; };