diff --git a/topology/icons/devices/cloud-server.svg b/topology/icons/devices/cloud-server.svg index 96e93ba..9912ff4 100644 --- a/topology/icons/devices/cloud-server.svg +++ b/topology/icons/devices/cloud-server.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/topology/icons/devices/router.svg b/topology/icons/devices/router.svg index 99ba7d4..0266fdb 100644 --- a/topology/icons/devices/router.svg +++ b/topology/icons/devices/router.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/topology/icons/devices/switch.svg b/topology/icons/devices/switch.svg index eaca268..13b4d31 100644 --- a/topology/icons/devices/switch.svg +++ b/topology/icons/devices/switch.svg @@ -1,3 +1 @@ - - - \ No newline at end of file + \ No newline at end of file diff --git a/topology/options/nodes.nix b/topology/options/nodes.nix index ac275e5..96bed20 100644 --- a/topology/options/nodes.nix +++ b/topology/options/nodes.nix @@ -104,11 +104,9 @@ in ); } - # If the device type is generic device, try to render as an image - # and set the default image to the deviceIcon. + # If the device type is not a full nixos node, try to render it as an image with name. (mkIf (elem nodeCfg.deviceType ["router" "switch" "device"]) { preferredRenderType = mkDefault "image"; - hardware.image = mkDefault (config.lib.icons.get nodeCfg.deviceIcon); }) ]); })); diff --git a/topology/topology/default.nix b/topology/topology/default.nix index 67d7dc7..8a014b2 100644 --- a/topology/topology/default.nix +++ b/topology/topology/default.nix @@ -45,9 +45,9 @@ in { }; renderer = mkOption { - description = "Which renderer to use for the default output. Availble options: ${toString availableRenderers}"; + description = "Which renderer to use for the default output. Available options: ${toString availableRenderers}"; type = types.nullOr (types.enum availableRenderers); - default = "d2"; + default = "elk"; }; output = mkOption { diff --git a/topology/topology/renderers/d2/network.nix b/topology/topology/renderers/d2/network.nix index b5d126a..c832dad 100644 --- a/topology/topology/renderers/d2/network.nix +++ b/topology/topology/renderers/d2/network.nix @@ -39,7 +39,7 @@ node_${node.id}: "" { shape: image width: 680 - icon: ${config.lib.renderers.svg.node.mkInfoCardNetwork node} + icon: ${config.lib.renderers.svg.node.mkPreferredRender node} } ${concatLines (map (nodeInterfaceToD2 node) (attrValues node.interfaces))} diff --git a/topology/topology/renderers/elk/default.nix b/topology/topology/renderers/elk/default.nix new file mode 100644 index 0000000..9a0479c --- /dev/null +++ b/topology/topology/renderers/elk/default.nix @@ -0,0 +1,91 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit + (lib) + any + attrValues + concatLines + flip + mkOption + optionalString + types + ; + + toBase64 = text: let + inherit (lib) sublist mod stringToCharacters concatMapStrings; + inherit (lib.strings) charToInt; + inherit (builtins) substring foldl' genList elemAt length concatStringsSep stringLength; + lookup = stringToCharacters "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789__"; + sliceN = size: list: n: sublist (n * size) size list; + pows = [(64 * 64 * 64) (64 * 64) 64 1]; + intSextets = i: map (j: mod (i / j) 64) pows; + compose = f: g: x: f (g x); + intToChar = elemAt lookup; + convertTripletInt = sliceInt: concatMapStrings intToChar (intSextets sliceInt); + sliceToInt = foldl' (acc: val: acc * 256 + val) 0; + convertTriplet = compose convertTripletInt sliceToInt; + join = concatStringsSep ""; + convertLastSlice = slice: let + len = length slice; + in + if len == 1 + then (substring 0 2 (convertTripletInt ((sliceToInt slice) * 256 * 256))) + "__" + else if len == 2 + then (substring 0 3 (convertTripletInt ((sliceToInt slice) * 256))) + "_" + else ""; + len = stringLength text; + nFullSlices = len / 3; + bytes = map charToInt (stringToCharacters text); + tripletAt = sliceN 3 bytes; + head = genList (compose convertTriplet tripletAt) nFullSlices; + tail = convertLastSlice (tripletAt nFullSlices); + in + join (head ++ [tail]); + + netToElk = net: '' + node net_${toBase64 net.id} { + label "${net.name}" + } + ''; + + nodeInterfaceToElk = node: interface: + concatLines (flip map interface.physicalConnections (x: + optionalString ( + (!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${x.node}.interfaces.${x.interface}.physicalConnections) + || (node.id < x.node) + ) + '' + edge node_${toBase64 node.id} -> node_${toBase64 x.node} + '')); + + nodeToElk = node: '' + node node_${toBase64 node.id} { + layout [size: 680, 0] + label "${node.name}" + } + + ${concatLines (map (nodeInterfaceToElk node) (attrValues node.interfaces))} + ''; +in { + options.renderers.elk = { + output = mkOption { + description = "The derivation containing the rendered output"; + type = types.path; + readOnly = true; + }; + }; + + config.renderers.elk.output = pkgs.writeText "graph.elk" '' + interactiveLayout: true + separateConnectedComponents: false + crossingMinimization.semiInteractive: true + elk.direction: RIGHT + + ${concatLines (map netToElk (attrValues config.networks))} + ${concatLines (map nodeToElk (attrValues config.nodes))} + ''; +} diff --git a/topology/topology/renderers/svg/default.nix b/topology/topology/renderers/svg/default.nix index 49b2438..b87c907 100644 --- a/topology/topology/renderers/svg/default.nix +++ b/topology/topology/renderers/svg/default.nix @@ -45,11 +45,10 @@ ''; renderHtmlToSvg = card: name: let - drv = pkgs.runCommand "generate-svg-${name}" {} '' - mkdir -p $out - ${htmlToSvgCommand (pkgs.writeText "${name}.html" card.html) "$out/${name}.svg" card} + out = pkgs.runCommand "${name}.svg" {} '' + ${htmlToSvgCommand (pkgs.writeText "${name}.html" card.html) "$out" card} ''; - in "${drv}/${name}.svg"; + in "${out}"; html = rec { mkImage = twAttrs: file: @@ -63,9 +62,9 @@ content = head (splitString "" withoutPrefix); in '''' else if hasSuffix ".png" file - then '''' else if hasSuffix ".jpg" file || hasSuffix ".jpeg" file - then '''' else builtins.throw "Unsupported icon file type: ${file}"; mkImageMaybeIf = cond: twAttrs: file: optionalString (cond && file != null) (mkImage twAttrs file); @@ -164,9 +163,8 @@ ''
${mkImageMaybe "w-12 h-12 mr-4" (config.lib.icons.get node.icon)} -

${node.name}

-
-

${node.deviceType}

+

${node.name}

+
${mkImageMaybe "w-16 h-16 ml-4" (config.lib.icons.get node.deviceIcon)}
''; @@ -214,20 +212,22 @@ html = let deviceIconImage = config.lib.icons.get node.deviceIcon; in - mkRootContainer "" + mkRootContainer "items-center" /* html */ ''
- ${mkImageMaybe "w-12 h-12 mr-4" (config.lib.icons.get node.icon)} -

${node.name}

-
-

${node.deviceType}

- ${mkImageMaybeIf (node.hardware.image != null -> deviceIconImage != node.hardware.image) "w-16 h-16 ml-4" deviceIconImage} + ${mkImageMaybe "w-12 h-12" (config.lib.icons.get node.icon)} +

${node.name}

+ ${optionalString (node.hardware.image != null -> deviceIconImage != node.hardware.image) + '' +
+ ${mkImageMaybe "w-16 h-16" deviceIconImage} + ''}
- ${mkImageMaybe "w-full h-24" node.hardware.image} + ${mkImageMaybe "h-24" node.hardware.image} ''; };