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 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

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 {
hidden = mkDefault true; # Causes a lot of much clutter
hidden = mkDefault true; # Causes a lot of clutter
name = "OpenSSH";
icon = "services.openssh";
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
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 {
@ -111,7 +111,7 @@ in
}
# 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";
})
]);

View file

@ -19,6 +19,7 @@
mapAttrsToList
mkOption
optional
optionals
optionalAttrs
recursiveUpdate
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:
[
{
(optionalAttrs (node.preferredRenderType == "card") {
children."node:${node.id}".ports."interface:${interface.id}" = {
properties = {
"port.side" = "WEST";
};
properties."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;
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 (
(!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 = ["children.node:${node.id}.ports.interface:${interface.id}"];
targets = ["children.node:${x.node}.ports.interface:${x.interface}"];
sources = [(idForInterface node interface.id)];
targets = [(idForInterface config.nodes.${x.node} x.interface)];
};
});
}));
nodeToElk = node:
[
@ -91,23 +103,21 @@
file = config.lib.renderers.svg.node.mkPreferredRender node;
scale = 0.8;
};
properties = {
"portConstraints" = "FIXED_SIDE";
};
properties."portConstraints" = "FIXED_SIDE";
};
}
]
++ optional (node.parent != null) {
children."node:${node.parent}".ports.guests = {
properties = {
"port.side" = "EAST";
};
properties."port.side" = "EAST";
width = 8;
height = 8;
};
edges."node:${node.parent}.ports.guests-to-node:${node.id}" = {
sources = ["children.node:${node.parent}.ports.guests"];
targets = ["children.node:${node.id}"];
style.stroke-dasharray = "10,8";
style.stroke-linecap = "round";
};
}
++ map (nodeInterfaceToElk node) (attrValues node.interfaces);
@ -128,10 +138,23 @@ in {
layoutOptions = {
"org.eclipse.elk.algorithm" = "layered";
"org.eclipse.elk.edgeRouting" = "ORTHOGONAL";
"org.eclipse.elk.direction" = "RIGHT";
"org.eclipse.elk.layered.crossingMinimization.strategy" = true;
"org.eclipse.elk.layered.nodePlacement.strategy" = "NETWORK_SIMPLEX";
"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
# - podman / docker harvesting
# - adjust device icon based on guest type
# - nixos-container extractor
{
config,
lib,
@ -116,8 +117,7 @@
*/
''
<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 mr-4">
<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">
${mkImage "w-6 h-6 mr-2" (config.lib.icons.get interface.icon)}
<span tw="font-bold">${interface.id}</span>
</div>
@ -141,7 +141,11 @@
# FIXME: order not respected
+ concatLines ((map serviceDetail) (attrValues service.details));
mkService = service:
mkService = {
additionalInfo ? "",
includeDetails ? true,
...
}: service:
/*
html
*/
@ -152,9 +156,10 @@
<div tw="flex flex-col grow">
<h1 tw="text-lg font-bold m-0">${service.name}</h1>
${optionalString (service.info != "") ''<p tw="text-sm m-0">${service.info}</p>''}
${additionalInfo}
</div>
</div>
${serviceDetails service}
${optionalString includeDetails (serviceDetails service)}
</div>
'';
@ -223,7 +228,7 @@
${concatLines (map mkGuest guests)}
${optionalString (guests != []) spacingMt2}
${concatLines (map mkService services)}
${concatLines (map (mkService {}) services)}
${optionalString (services != []) spacingMt2}
${mkImageMaybe "w-full h-24" node.hardware.image}
@ -240,12 +245,12 @@
*/
''
<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>
${optionalString (node.hardware.image != null -> deviceIconImage != node.hardware.image)
''
<div tw="flex grow min-w-8"></div>
${mkImageMaybe "w-16 h-16" deviceIconImage}
<div tw="flex grow min-w-4"></div>
${mkImageMaybe "w-12 h-12" deviceIconImage}
''}
</div>
@ -261,6 +266,36 @@
)
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 {
options.renderers.svg = {
@ -274,10 +309,14 @@ in {
};
config = {
lib.renderers.svg.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}";
lib.renderers.svg = {
services.mkOverview = renderHtmlToSvg html.services.mkOverview "services-overview";
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" {} ''