mirror of
https://github.com/oddlama/nix-config.git
synced 2025-10-11 07:10:39 +02:00
feat(topology): add services overview card
This commit is contained in:
parent
07191bac9b
commit
b822b4e812
8 changed files with 177 additions and 67 deletions
1
cloud.svg
Normal file
1
cloud.svg
Normal 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 |
86
flake.nix
86
flake.nix
|
@ -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 {};
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
29
topology/nixos/extractors/microvm.nix
Normal file
29
topology/nixos/extractors/microvm.nix
Normal 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;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
));
|
||||||
|
};
|
||||||
|
}
|
|
@ -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)}";
|
||||||
|
|
|
@ -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";
|
||||||
})
|
})
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -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;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -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" {} ''
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue