feat(topology): add services overview card

This commit is contained in:
oddlama 2024-03-28 01:39:31 +01:00
parent 07191bac9b
commit b822b4e812
No known key found for this signature in database
GPG key ID: 14EFE510775FE39A
8 changed files with 177 additions and 67 deletions

1
cloud.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="400" aria-hidden="true" class="iconify iconify--noto" viewBox="0 0 128 128"><path fill="#e4eaee" d="M23.45 62.3c.72-.72-1.27-9.29 7.6-15.91s14.92-2.67 15.77-2.96c.84-.28 4.79-17.6 21.4-22.1s33.93 3.94 38.01 18.02c3.73 12.87.84 21.54 1.27 22.1.42.56 8.45.28 13.09 7.74s2.96 12.11 2.96 12.11l-29.56 9.15h-47.3S5.02 79.47 4.6 77.5s.53-8.37 7.32-12.25c5.9-3.37 10.26-1.68 11.53-2.95"/><path fill="#bacdd2" d="M35.16 92.84s-15.78 3.3-26.45-4.96C2.29 82.9 4.63 74.83 4.63 74.83s4.6 4.65 13.89 5.91c9.29 1.27 19.71.84 19.71.84s2.6 4.44 12.39 6.48c12.27 2.55 18.74-3.73 18.74-3.73s3.36 4.02 15.19 4.3 18.46-7.98 19.57-8.17c.56-.09 3.82 2.87 10.28 1.83 6.15-.99 9.39-3.66 9.39-3.66s.89 6.62-5.3 10.7c-4.83 3.18-13.23 3.52-13.23 3.52s-1.28 4.91-7.05 8.48c-5.36 3.33-14.6 4.44-21.44 2.4-8.59-2.56-10.72-6.47-10.72-6.47s-6.4 3.75-16.4 2.48c-9.45-1.18-14.49-6.9-14.49-6.9"/></svg>

After

Width:  |  Height:  |  Size: 929 B

View file

@ -192,47 +192,69 @@
deviceType = "router"; deviceType = "router";
hardware.image = ./fritzbox.png; hardware.image = ./fritzbox.png;
# interfaces.wan0.network = "internet"; # interfaces.wan0.network = "internet";
interfaces.wan0.physicalConnections = [ interfaces.wan0 = {};
interfaces.lan0.physicalConnections = [
{ {
node = "ward"; node = "ward";
interface = "wan"; interface = "wan";
} }
{
node = "sire";
interface = "lan";
}
];
};
nodes.internet = {
name = "Internet";
deviceType = "internet";
hardware.image = ./cloud.svg;
# interfaces.wan0.network = "internet";
interfaces.wan0.physicalConnections = [
{
node = "fritzbox";
interface = "wan0";
}
{
node = "sentinel";
interface = "wan";
}
]; ];
}; };
nodes.fritzbox-no-img = { #nodes.fritzbox-no-img = {
name = "FritzBox No HImg"; # name = "FritzBox No HImg";
deviceType = "router"; # deviceType = "router";
interfaces.wan0.physicalConnections = [ # interfaces.wan0.physicalConnections = [
{ # {
node = "ward"; # node = "ward";
interface = "wan"; # interface = "wan";
} # }
]; # ];
}; #};
nodes.fritzbox-device-nd = { #nodes.fritzbox-device-nd = {
name = "FritzBox No DImg"; # name = "FritzBox No DImg";
deviceType = "device"; # deviceType = "device";
hardware.image = ./fritzbox.png; # hardware.image = ./fritzbox.png;
interfaces.wan0.physicalConnections = [ # interfaces.wan0.physicalConnections = [
{ # {
node = "ward"; # node = "ward";
interface = "wan"; # interface = "wan";
} # }
]; # ];
}; #};
nodes.fritzbox-device = { #nodes.fritzbox-device = {
name = "FritzBox No D&HImg"; # name = "FritzBox No D&HImg";
deviceType = "device"; # deviceType = "device";
interfaces.wan0.physicalConnections = [ # interfaces.wan0.physicalConnections = [
{ # {
node = "ward"; # node = "ward";
interface = "wan"; # interface = "wan";
} # }
]; # ];
}; #};
# TODO: # TODO:
#nodes.fritzbox = config.lib.nodes.mkRouter {}; #nodes.fritzbox = config.lib.nodes.mkRouter {};

View file

@ -64,10 +64,6 @@
networking.nftables.firewall = { networking.nftables.firewall = {
zones.untrusted.interfaces = [config.guests.${guestName}.networking.mainLinkName]; zones.untrusted.interfaces = [config.guests.${guestName}.networking.mainLinkName];
}; };
# TODO: FIXME: remove!!!!
topology.self.guestType = "microvm";
topology.self.parent = config.node.name;
} }
]; ];
}; };

View file

@ -0,0 +1,29 @@
{
config,
lib,
...
}: let
inherit
(lib)
attrValues
flip
mkEnableOption
mkIf
mkMerge
optionalAttrs
;
in {
options.topology.extractors.microvm.enable = mkEnableOption "topology microvm extractor" // {default = true;};
config = mkIf (config.topology.extractors.microvm.enable && config ? microvm && config.microvm.host.enable) {
topology.nodes = mkMerge (flip map (attrValues config.microvm.vms) (
vm:
optionalAttrs (vm.config.config ? topology) {
${vm.config.config.topology.id} = {
guestType = "microvm";
parent = config.topology.id;
};
}
));
};
}

View file

@ -60,7 +60,7 @@ in {
}; };
openssh = mkIf config.services.openssh.enable { openssh = mkIf config.services.openssh.enable {
hidden = mkDefault true; # Causes a lot of much clutter hidden = mkDefault true; # Causes a lot of clutter
name = "OpenSSH"; name = "OpenSSH";
icon = "services.openssh"; icon = "services.openssh";
info = "port: ${concatStringsSep ", " (map toString config.services.openssh.ports)}"; info = "port: ${concatStringsSep ", " (map toString config.services.openssh.ports)}";

View file

@ -66,7 +66,7 @@ in
values exist that will automatically set some other defaults, most notably values exist that will automatically set some other defaults, most notably
the deviceIcon and preferredRenderType. the deviceIcon and preferredRenderType.
''; '';
type = types.either (types.enum ["nixos" "router" "switch" "device"]) types.str; type = types.either (types.enum ["nixos" "internet" "router" "switch" "device"]) types.str;
}; };
guestType = mkOption { guestType = mkOption {
@ -111,7 +111,7 @@ in
} }
# If the device type is not a full nixos node, try to render it as an image with name. # 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"]) { (mkIf (elem nodeCfg.deviceType ["internet" "router" "switch" "device"]) {
preferredRenderType = mkDefault "image"; preferredRenderType = mkDefault "image";
}) })
]); ]);

View file

@ -19,6 +19,7 @@
mapAttrsToList mapAttrsToList
mkOption mkOption
optional optional
optionals
optionalAttrs optionalAttrs
recursiveUpdate recursiveUpdate
types types
@ -60,28 +61,39 @@
}; };
}; };
idForInterface = node: interfaceId:
if (node.preferredRenderType == "card")
then "children.node:${node.id}.ports.interface:${interfaceId}"
else "children.node:${node.id}";
nodeInterfaceToElk = node: interface: nodeInterfaceToElk = node: interface:
[ [
{ (optionalAttrs (node.preferredRenderType == "card") {
children."node:${node.id}".ports."interface:${interface.id}" = { children."node:${node.id}".ports."interface:${interface.id}" = {
properties = { properties."port.side" = "WEST";
"port.side" = "WEST"; #x = 0;
}; #y = 82 + 42 * lib.lists.findFirstIndex (x: x == interface.id) 0 (builtins.attrNames node.interfaces); # FIXME: just pass index along in function call
width = 8; width = 8;
height = 8; height = 8;
# TODO: FIXME: not shown currently in svg
# labels.name = {
# text = interface.id;
# width = 33.0;
# height = 15.0;
# };
}; };
} })
] ]
++ flip map interface.physicalConnections (x: ++ optionals (!interface.virtual) (flip map interface.physicalConnections (x:
optionalAttrs ( optionalAttrs (
(!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${x.node}.interfaces.${x.interface}.physicalConnections) (!any (y: y.node == node.id && y.interface == interface.id) config.nodes.${x.node}.interfaces.${x.interface}.physicalConnections)
|| (node.id < x.node) || (node.id < x.node)
) { ) {
edges."node:${node.id}.ports.interface:${interface.id}-to-node:${x.node}.ports.interface:${x.interface}" = { edges."node:${node.id}.ports.interface:${interface.id}-to-node:${x.node}.ports.interface:${x.interface}" = {
sources = ["children.node:${node.id}.ports.interface:${interface.id}"]; sources = [(idForInterface node interface.id)];
targets = ["children.node:${x.node}.ports.interface:${x.interface}"]; targets = [(idForInterface config.nodes.${x.node} x.interface)];
}; };
}); }));
nodeToElk = node: nodeToElk = node:
[ [
@ -91,23 +103,21 @@
file = config.lib.renderers.svg.node.mkPreferredRender node; file = config.lib.renderers.svg.node.mkPreferredRender node;
scale = 0.8; scale = 0.8;
}; };
properties = { properties."portConstraints" = "FIXED_SIDE";
"portConstraints" = "FIXED_SIDE";
};
}; };
} }
] ]
++ optional (node.parent != null) { ++ optional (node.parent != null) {
children."node:${node.parent}".ports.guests = { children."node:${node.parent}".ports.guests = {
properties = { properties."port.side" = "EAST";
"port.side" = "EAST";
};
width = 8; width = 8;
height = 8; height = 8;
}; };
edges."node:${node.parent}.ports.guests-to-node:${node.id}" = { edges."node:${node.parent}.ports.guests-to-node:${node.id}" = {
sources = ["children.node:${node.parent}.ports.guests"]; sources = ["children.node:${node.parent}.ports.guests"];
targets = ["children.node:${node.id}"]; targets = ["children.node:${node.id}"];
style.stroke-dasharray = "10,8";
style.stroke-linecap = "round";
}; };
} }
++ map (nodeInterfaceToElk node) (attrValues node.interfaces); ++ map (nodeInterfaceToElk node) (attrValues node.interfaces);
@ -128,10 +138,23 @@ in {
layoutOptions = { layoutOptions = {
"org.eclipse.elk.algorithm" = "layered"; "org.eclipse.elk.algorithm" = "layered";
"org.eclipse.elk.edgeRouting" = "ORTHOGONAL"; "org.eclipse.elk.edgeRouting" = "ORTHOGONAL";
"org.eclipse.elk.direction" = "RIGHT";
"org.eclipse.elk.layered.crossingMinimization.strategy" = true; "org.eclipse.elk.layered.crossingMinimization.strategy" = true;
"org.eclipse.elk.layered.nodePlacement.strategy" = "NETWORK_SIMPLEX"; "org.eclipse.elk.layered.nodePlacement.strategy" = "NETWORK_SIMPLEX";
"org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers" = 40; "org.eclipse.elk.layered.spacing.edgeNodeBetweenLayers" = 40;
"org.eclipse.elk.direction" = "RIGHT"; "org.eclipse.elk.layered.spacing.edgeEdgeBetweenLayers" = 25;
"org.eclipse.elk.spacing.edgeEdge" = 50;
"org.eclipse.elk.spacing.edgeNode" = 50;
};
}
# Add service overview
{
children.services-overview = {
svg = {
file = config.lib.renderers.svg.services.mkOverview;
scale = 0.8;
};
}; };
} }
] ]

View file

@ -10,6 +10,7 @@
# - search todo and do # - search todo and do
# - podman / docker harvesting # - podman / docker harvesting
# - adjust device icon based on guest type # - adjust device icon based on guest type
# - nixos-container extractor
{ {
config, config,
lib, lib,
@ -116,8 +117,7 @@
*/ */
'' ''
<div tw="flex flex-row items-center my-2"> <div tw="flex flex-row items-center my-2">
<div tw="flex flex-row flex-none bg-[${color}] w-4 h-1"></div> <div tw="flex flex-row flex-none items-center bg-[${color}] text-[#101419] rounded-lg px-2 py-1 w-46 h-8 mx-4">
<div tw="flex flex-row flex-none items-center bg-[${color}] text-[#101419] rounded-lg px-2 py-1 w-46 h-8 mr-4">
${mkImage "w-6 h-6 mr-2" (config.lib.icons.get interface.icon)} ${mkImage "w-6 h-6 mr-2" (config.lib.icons.get interface.icon)}
<span tw="font-bold">${interface.id}</span> <span tw="font-bold">${interface.id}</span>
</div> </div>
@ -141,7 +141,11 @@
# FIXME: order not respected # FIXME: order not respected
+ concatLines ((map serviceDetail) (attrValues service.details)); + concatLines ((map serviceDetail) (attrValues service.details));
mkService = service: mkService = {
additionalInfo ? "",
includeDetails ? true,
...
}: service:
/* /*
html html
*/ */
@ -152,9 +156,10 @@
<div tw="flex flex-col grow"> <div tw="flex flex-col grow">
<h1 tw="text-lg font-bold m-0">${service.name}</h1> <h1 tw="text-lg font-bold m-0">${service.name}</h1>
${optionalString (service.info != "") ''<p tw="text-sm m-0">${service.info}</p>''} ${optionalString (service.info != "") ''<p tw="text-sm m-0">${service.info}</p>''}
${additionalInfo}
</div> </div>
</div> </div>
${serviceDetails service} ${optionalString includeDetails (serviceDetails service)}
</div> </div>
''; '';
@ -223,7 +228,7 @@
${concatLines (map mkGuest guests)} ${concatLines (map mkGuest guests)}
${optionalString (guests != []) spacingMt2} ${optionalString (guests != []) spacingMt2}
${concatLines (map mkService services)} ${concatLines (map (mkService {}) services)}
${optionalString (services != []) spacingMt2} ${optionalString (services != []) spacingMt2}
${mkImageMaybe "w-full h-24" node.hardware.image} ${mkImageMaybe "w-full h-24" node.hardware.image}
@ -240,12 +245,12 @@
*/ */
'' ''
<div tw="flex flex-row mx-6 mt-2 items-center"> <div tw="flex flex-row mx-6 mt-2 items-center">
${mkImageMaybe "w-12 h-12" (config.lib.icons.get node.icon)} ${mkImageMaybe "w-8 h-8" (config.lib.icons.get node.icon)}
<h2 tw="text-2xl font-bold">${node.name}</h2> <h2 tw="text-2xl font-bold">${node.name}</h2>
${optionalString (node.hardware.image != null -> deviceIconImage != node.hardware.image) ${optionalString (node.hardware.image != null -> deviceIconImage != node.hardware.image)
'' ''
<div tw="flex grow min-w-8"></div> <div tw="flex grow min-w-4"></div>
${mkImageMaybe "w-16 h-16" deviceIconImage} ${mkImageMaybe "w-12 h-12" deviceIconImage}
''} ''}
</div> </div>
@ -261,6 +266,36 @@
) )
node; node;
}; };
services.mkOverview = {
width = 480;
html =
mkCardContainer
/*
html
*/
''
<div tw="flex flex-row mx-6 mt-2 items-center">
<h2 tw="text-2xl font-bold">Services Overview</h2>
</div>
${concatLines (flip map (attrValues config.nodes) (
node: let
services = filter (x: !x.hidden) (attrValues node.services);
in
concatLines (
flip map services (
html.node.mkService {
additionalInfo = ''<p tw="text-sm text-[#7a899f] m-0">${node.name}</p>'';
includeDetails = false;
}
)
)
))}
${spacingMt2}
'';
};
}; };
in { in {
options.renderers.svg = { options.renderers.svg = {
@ -274,10 +309,14 @@ in {
}; };
config = { config = {
lib.renderers.svg.node = { lib.renderers.svg = {
mkNetCard = node: renderHtmlToSvg (html.node.mkNetCard node) "card-network-${node.id}"; services.mkOverview = renderHtmlToSvg html.services.mkOverview "services-overview";
mkCard = node: renderHtmlToSvg (html.node.mkCard node) "card-node-${node.id}";
mkPreferredRender = node: renderHtmlToSvg (html.node.mkPreferredRender node) "preferred-render-node-${node.id}"; node = {
mkNetCard = node: renderHtmlToSvg (html.node.mkNetCard node) "card-network-${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}";
};
}; };
renderers.svg.output = pkgs.runCommand "topology-svgs" {} '' renderers.svg.output = pkgs.runCommand "topology-svgs" {} ''